From f6d3eef2cfc09f5017965164cd2187123e4006c4 Mon Sep 17 00:00:00 2001 From: Khafidh Fuadi Date: Thu, 27 Mar 2025 16:55:56 +0700 Subject: [PATCH] Perbarui beberapa file konfigurasi fingerprint untuk arsitektur arm64-v8a, armeabi-v7a, x86, dan x86_64. Modifikasi tampilan dan controller di modul donatur dan petugas desa untuk meningkatkan pengalaman pengguna, termasuk penggantian logika pengambilan data dan penyesuaian tampilan. Hapus kode yang tidak digunakan dan tambahkan fungsionalitas baru untuk mendukung pengelolaan data yang lebih baik. --- .../arm64-v8a/configure_fingerprint.bin | 24 +- .../armeabi-v7a/configure_fingerprint.bin | 24 +- .../626b5o2n/x86/configure_fingerprint.bin | 24 +- .../626b5o2n/x86_64/configure_fingerprint.bin | 24 +- .../donatur_dashboard_controller.dart | 3 - .../donatur/views/donatur_penitipan_view.dart | 1 - .../components/calendar_view_widget.dart | 16 +- .../components/jadwal_section_widget.dart | 7 +- .../controllers/counter_service.dart | 6 + .../detail_penyaluran_controller.dart | 21 + .../jadwal_penyaluran_controller.dart | 10 +- .../pelaksanaan_penyaluran_controller.dart | 42 +- .../controllers/petugas_desa_controller.dart | 42 +- .../petugas_desa_dashboard_controller.dart | 36 +- .../controllers/riwayat_stok_controller.dart | 22 + .../petugas_desa/views/dashboard_view.dart | 33 +- .../views/konfirmasi_penerima_page.dart | 28 +- .../petugas_desa/views/pengaduan_view.dart | 605 +++--- .../petugas_desa/views/penitipan_view.dart | 601 ++++-- .../petugas_desa/views/penyaluran_view.dart | 6 +- .../petugas_desa/views/petugas_desa_view.dart | 7 +- .../petugas_desa/views/riwayat_stok_view.dart | 1665 ++++++++++++++--- .../petugas_desa/views/stok_bantuan_view.dart | 403 ++-- .../views/tambah_penyaluran_view.dart | 174 +- .../warga_dashboard_controller.dart | 71 +- .../warga/views/form_pengaduan_view.dart | 306 +++ .../warga/views/warga_dashboard_view.dart | 189 +- .../views/warga_detail_penerimaan_view.dart | 26 +- .../warga/views/warga_pengaduan_view.dart | 60 +- lib/app/modules/warga/views/warga_view.dart | 20 +- lib/app/services/supabase_service.dart | 215 ++- 31 files changed, 3372 insertions(+), 1339 deletions(-) create mode 100644 lib/app/modules/warga/views/form_pengaduan_view.dart diff --git a/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin index 9d5bd8b..6cb9aff 100644 --- a/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  Ӊ2 2 +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  2 2  -}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  Ӊ2  2~ +}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  2  2~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  Ӊ2 2 +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  2 2  -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  Ӊ2 2p +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  2 2p n -lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  Ӊ2 2t +lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  2 2t r -pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  Ӊ2y +pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  2y w -uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  Ӊ2 K 2z +uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  2 K 2z x -vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  Ӊ2 ~ +vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  2 ~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  Ӊ2 +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  2   -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  Ӊ2  2w +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  2  2w u -sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  Ӊ2  ( 2| +sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  2  ( 2| z -xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\symbol_folder_index.txt  Ӊ2  o 2 \ No newline at end of file +xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\symbol_folder_index.txt  2  o 2 \ No newline at end of file diff --git a/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin index b8c596e..0e41553 100644 --- a/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ˝Ӊ2 2 +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  2 2  -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  ̝Ӊ2  2 +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  2  2 ~ -|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  ̝Ӊ2 2 +|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  2 2  -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  ̝Ӊ2 2r +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  2 2r p -nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  ̝Ӊ2 2v +nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  2 2v t -rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  ΝӉ2{ +rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  2{ y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  ΝӉ2 K 2| +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  2 K 2| z -xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json  ΝӉ2  +xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json  2  ~ -|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json.bin  ΝӉ2 +|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json.bin  2   -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\metadata_generation_command.txt  НӉ2  2y +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\metadata_generation_command.txt  2  2y w -uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\prefab_config.json  НӉ2  ( 2~ +uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\prefab_config.json  2  ( 2~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\symbol_folder_index.txt  НӉ2  q 2 \ No newline at end of file +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\symbol_folder_index.txt  2  q 2 \ No newline at end of file diff --git a/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin index e87ab7d..c374897 100644 --- a/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  Ӊ2 2{ +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  2 2{ y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  Ӊ2  2x +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  2  2x v -tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  Ӊ2 2} +tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  2 2} { -yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  Ӊ2 2j +yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  2 2j h -fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  Ӊ2 2n +fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  2 2n l -jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  Ӊ2s +jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  2s q -oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  Ӊ2 K 2t +oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  2 K 2t r -pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json  Ӊ2 x +pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json  2 x v -tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin  Ӊ2 +tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin  2 ~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt  Ӊ2  2q +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt  2  2q o -mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json  Ӊ2  ( 2v +mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json  2  ( 2v t -rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\symbol_folder_index.txt  Ӊ2  i 2 \ No newline at end of file +rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\symbol_folder_index.txt  2  i 2 \ No newline at end of file diff --git a/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin index 14c3659..73ed389 100644 --- a/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ȠӉ2 2~ +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  2 2~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  ȠӉ2  2{ +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  2  2{ y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  ȠӉ2 2 +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  2 2 ~ -|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  ϠӉ2 2m +|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  2 2m k -iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  ϠӉ2 2q +iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  2 2q o -mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  ϠӉ2v +mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  2v t -rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  ڠӉ2 K 2w +rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  2 K 2w u -sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json  ڠӉ2 { +sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json  2 { y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  ڠӉ2 +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  2   -}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  ڠӉ2  2t +}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  2  2t r -pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\prefab_config.json  ڠӉ2  ( 2y +pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\prefab_config.json  2  ( 2y w -uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\symbol_folder_index.txt  ڠӉ2  l 2 \ No newline at end of file +uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\symbol_folder_index.txt  2  l 2 \ No newline at end of file diff --git a/lib/app/modules/donatur/controllers/donatur_dashboard_controller.dart b/lib/app/modules/donatur/controllers/donatur_dashboard_controller.dart index a047441..5d100b5 100644 --- a/lib/app/modules/donatur/controllers/donatur_dashboard_controller.dart +++ b/lib/app/modules/donatur/controllers/donatur_dashboard_controller.dart @@ -11,9 +11,6 @@ import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart import 'package:penyaluran_app/app/services/supabase_service.dart'; import 'package:penyaluran_app/app/routes/app_pages.dart'; import 'package:image_picker/image_picker.dart'; -import 'dart:io'; - -import 'package:supabase_flutter/supabase_flutter.dart'; class DonaturDashboardController extends GetxController { final AuthController _authController = Get.find(); diff --git a/lib/app/modules/donatur/views/donatur_penitipan_view.dart b/lib/app/modules/donatur/views/donatur_penitipan_view.dart index b4195ba..cdbbf58 100644 --- a/lib/app/modules/donatur/views/donatur_penitipan_view.dart +++ b/lib/app/modules/donatur/views/donatur_penitipan_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:intl/intl.dart'; import 'package:penyaluran_app/app/data/models/skema_bantuan_model.dart'; import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart'; import 'package:penyaluran_app/app/widgets/section_header.dart'; diff --git a/lib/app/modules/petugas_desa/components/calendar_view_widget.dart b/lib/app/modules/petugas_desa/components/calendar_view_widget.dart index eb66dd6..903a07e 100644 --- a/lib/app/modules/petugas_desa/components/calendar_view_widget.dart +++ b/lib/app/modules/petugas_desa/components/calendar_view_widget.dart @@ -8,11 +8,16 @@ import 'package:penyaluran_app/app/utils/date_time_helper.dart'; class CalendarViewWidget extends StatelessWidget { final JadwalPenyaluranController controller; + final CalendarController _calendarController = CalendarController(); - const CalendarViewWidget({ + CalendarViewWidget({ super.key, required this.controller, - }); + }) { + // Mengatur controller kalender untuk selalu memilih hari ini saat inisialisasi + _calendarController.selectedDate = DateTime.now(); + _calendarController.displayDate = DateTime.now(); + } @override Widget build(BuildContext context) { @@ -49,6 +54,9 @@ class CalendarViewWidget extends StatelessWidget { child: Obx(() { return SfCalendar( view: CalendarView.month, + controller: _calendarController, + initialSelectedDate: DateTime.now(), + initialDisplayDate: DateTime.now(), dataSource: _getCalendarDataSource(), timeZone: 'Asia/Jakarta', monthViewSettings: MonthViewSettings( @@ -246,7 +254,7 @@ class CalendarViewWidget extends StatelessWidget { List appointments = []; List allJadwal = [ - ...controller.jadwalHariIni, + ...controller.jadwalAktif, ...controller.jadwalMendatang, ...controller.jadwalTerlaksana, ]; @@ -556,7 +564,7 @@ class CalendarViewWidget extends StatelessWidget { // Cari jadwal dengan ID yang sesuai for (var jadwal in [ - ...controller.jadwalHariIni, + ...controller.jadwalAktif, ...controller.jadwalMendatang, ...controller.jadwalTerlaksana ]) { diff --git a/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart index 2ae8853..f244283 100644 --- a/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart +++ b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart @@ -165,9 +165,10 @@ class JadwalSectionWidget extends StatelessWidget { List _getCurrentJadwalList() { switch (title) { - case 'Hari Ini': - return controller.jadwalHariIni.toList(); - case 'Mendatang': + case 'Penyaluran Aktif': + return controller.jadwalAktif.toList(); + + case '7 Hari Mendatang': return controller.jadwalMendatang.toList(); case 'Terlaksana': return controller.jadwalTerlaksana.toList(); diff --git a/lib/app/modules/petugas_desa/controllers/counter_service.dart b/lib/app/modules/petugas_desa/controllers/counter_service.dart index c379da8..c75b622 100644 --- a/lib/app/modules/petugas_desa/controllers/counter_service.dart +++ b/lib/app/modules/petugas_desa/controllers/counter_service.dart @@ -73,17 +73,23 @@ class CounterService extends GetxService { void updatePengaduanCounter(int diproses) { jumlahDiproses.value = diproses; _storage.write(_keyDiproses, diproses); + + print('Counter pengaduan updated and saved - Diproses: $diproses'); } // Metode untuk memperbarui counter notifikasi void updateNotifikasiCounter(int belumDibaca) { jumlahNotifikasiBelumDibaca.value = belumDibaca; _storage.write(_keyNotifikasi, belumDibaca); + + print('Counter notifikasi updated and saved - Belum Dibaca: $belumDibaca'); } // Metode untuk memperbarui counter jadwal void updateJadwalCounter(int hariIni) { jumlahJadwalHariIni.value = hariIni; _storage.write(_keyJadwal, hariIni); + + print('Counter jadwal updated and saved - Hari Ini: $hariIni'); } } diff --git a/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart b/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart index 38653e2..18cfcfb 100644 --- a/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart @@ -204,6 +204,27 @@ class DetailPenyaluranController extends GetxController { .update(updateData) .eq('id', penerima.id!); + // Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah) + final penerimaData = await _supabaseService.client + .from('penerima_penyaluran') + .select('penyaluran_bantuan_id, stok_bantuan_id, jumlah_bantuan') + .eq('id', penerima.id!) + .single(); + + if (penerimaData != null) { + final String stokBantuanId = penerimaData['stok_bantuan_id']; + final double jumlah = penerimaData['jumlah_bantuan'] is int + ? penerimaData['jumlah_bantuan'].toDouble() + : penerimaData['jumlah_bantuan']; + + // Kurangi stok dan catat riwayat + final petugasId = _supabaseService.client.auth.currentUser?.id; + if (petugasId != null) { + await _supabaseService.kurangiStokDariPenyaluran( + penerima.id!, stokBantuanId, jumlah, petugasId); + } + } + // Refresh data setelah konfirmasi berhasil await refreshData(); diff --git a/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart b/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart index 291211c..03ec793 100644 --- a/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart @@ -24,7 +24,7 @@ class JadwalPenyaluranController extends GetxController { final RxInt selectedCategoryIndex = 0.obs; // Data untuk jadwal - final RxList jadwalHariIni = + final RxList jadwalAktif = [].obs; final RxList jadwalMendatang = [].obs; @@ -97,7 +97,7 @@ class JadwalPenyaluranController extends GetxController { List jadwalToUpdate = []; List jadwalTerlewat = []; - for (var jadwal in jadwalHariIni) { + for (var jadwal in jadwalAktif) { if (jadwal.tanggalPenyaluran != null) { final jadwalDateTime = DateTimeHelper.toLocalDateTime(jadwal.tanggalPenyaluran!); @@ -175,9 +175,9 @@ class JadwalPenyaluranController extends GetxController { isLoading.value = true; try { // Mengambil data jadwal hari ini - final jadwalHariIniData = await _supabaseService.getJadwalHariIni(); - if (jadwalHariIniData != null) { - jadwalHariIni.value = jadwalHariIniData + final jadwalAktifData = await _supabaseService.getJadwalAktif(); + if (jadwalAktifData != null) { + jadwalAktif.value = jadwalAktifData .map((data) => PenyaluranBantuanModel.fromJson(data)) .toList(); } diff --git a/lib/app/modules/petugas_desa/controllers/pelaksanaan_penyaluran_controller.dart b/lib/app/modules/petugas_desa/controllers/pelaksanaan_penyaluran_controller.dart index 007b6e1..e88c041 100644 --- a/lib/app/modules/petugas_desa/controllers/pelaksanaan_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/pelaksanaan_penyaluran_controller.dart @@ -250,29 +250,29 @@ class PelaksanaanPenyaluranController extends GetxController { filteredPenerima.value = filtered; } - // Metode untuk memperbarui status penerimaan bantuan - Future updateStatusPenerimaan(int penerimaId, String status, - {DateTime? tanggalPenerimaan, - String? buktiPenerimaan, - String? keterangan}) async { - try { - final result = await supabaseService.updateStatusPenerimaan( - penerimaId, status, - tanggalPenerimaan: tanggalPenerimaan, - buktiPenerimaan: buktiPenerimaan, - keterangan: keterangan); + // // Metode untuk memperbarui status penerimaan bantuan + // Future updateStatusPenerimaan(int penerimaId, String status, + // {DateTime? tanggalPenerimaan, + // String? buktiPenerimaan, + // String? keterangan}) async { + // try { + // final result = await supabaseService.updateStatusPenerimaan( + // penerimaId, status, + // tanggalPenerimaan: tanggalPenerimaan, + // buktiPenerimaan: buktiPenerimaan, + // keterangan: keterangan); - // Jika berhasil, perbarui data lokal - if (result) { - await loadPenerimaPenyaluran(activePenyaluranId.value); - } + // // Jika berhasil, perbarui data lokal + // if (result) { + // await loadPenerimaPenyaluran(activePenyaluranId.value); + // } - return result; - } catch (e) { - print('Error updating status penerimaan: $e'); - return false; - } - } + // return result; + // } catch (e) { + // print('Error updating status penerimaan: $e'); + // return false; + // } + // } // Metode untuk menyelesaikan jadwal penyaluran Future completeJadwal(String jadwalId) async { diff --git a/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart b/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart index 576e762..676f3f2 100644 --- a/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart @@ -309,10 +309,10 @@ class PetugasDesaController extends GetxController { // Metode untuk memuat data jadwal Future loadJadwalData() async { try { - final jadwalHariIniData = await _supabaseService.getJadwalHariIni(); - if (jadwalHariIniData != null) { - jadwalHariIni.value = jadwalHariIniData; - _counterService.updateJadwalCounter(jadwalHariIniData.length); + final jadwalAktifData = await _supabaseService.getJadwalAktif(); + if (jadwalAktifData != null) { + jadwalHariIni.value = jadwalAktifData; + _counterService.updateJadwalCounter(jadwalAktifData.length); } } catch (e) { print('Error loading jadwal data: $e'); @@ -360,7 +360,7 @@ class PetugasDesaController extends GetxController { // Hitung jumlah pengaduan dengan status DIPROSES for (var item in pengaduanData) { - if (item['status'] == 'DIPROSES') { + if (item['status'] == 'MENUNGGU') { diproses++; } } @@ -609,22 +609,22 @@ class PetugasDesaController extends GetxController { } // Metode untuk memperbarui status penerimaan bantuan - Future updateStatusPenerimaan(int penerimaId, String status, - {DateTime? tanggalPenerimaan, - String? buktiPenerimaan, - String? keterangan}) async { - try { - final result = await _supabaseService.updateStatusPenerimaan( - penerimaId, status, - tanggalPenerimaan: tanggalPenerimaan, - buktiPenerimaan: buktiPenerimaan, - keterangan: keterangan); - return result; - } catch (e) { - print('Error updating status penerimaan: $e'); - return false; - } - } + // Future updateStatusPenerimaan(int penerimaId, String status, + // {DateTime? tanggalPenerimaan, + // String? buktiPenerimaan, + // String? keterangan}) async { + // try { + // final result = await _supabaseService.updateStatusPenerimaan( + // penerimaId, status, + // tanggalPenerimaan: tanggalPenerimaan, + // buktiPenerimaan: buktiPenerimaan, + // keterangan: keterangan); + // return result; + // } catch (e) { + // print('Error updating status penerimaan: $e'); + // return false; + // } + // } // Metode untuk menyelesaikan jadwal penyaluran Future completeJadwal(String jadwalId) async { diff --git a/lib/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart b/lib/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart index e2f1034..79ab5a7 100644 --- a/lib/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart @@ -4,10 +4,12 @@ import 'package:penyaluran_app/app/data/models/user_model.dart'; import 'package:penyaluran_app/app/data/models/notifikasi_model.dart'; import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; import 'package:penyaluran_app/app/services/supabase_service.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/counter_service.dart'; class PetugasDesaDashboardController extends GetxController { final AuthController _authController = Get.find(); final SupabaseService _supabaseService = SupabaseService.to; + late final CounterService _counterService; final RxBool isLoading = false.obs; @@ -22,6 +24,12 @@ class PetugasDesaDashboardController extends GetxController { final RxInt totalPenitipanTerverifikasi = 0.obs; final RxDouble progressPenyaluran = 0.0.obs; + // Data untuk status penyaluran + final RxInt penyaluranDijadwalkan = 0.obs; + final RxInt penyaluranAktif = 0.obs; + final RxInt penyaluranBatal = 0.obs; + final RxInt penyaluranTerlaksana = 0.obs; + // Data untuk notifikasi final RxList notifikasiBelumDibaca = [].obs; final RxInt jumlahNotifikasiBelumDibaca = 0.obs; @@ -45,13 +53,24 @@ class PetugasDesaDashboardController extends GetxController { userProfile['desa']?['nama'] ?? (userProfile['desa_id'] != null ? 'Desa' : 'Desa'); + // Getter untuk counter dari CounterService + RxInt get jumlahMenunggu => _counterService.jumlahMenunggu; + RxInt get jumlahDiproses => _counterService.jumlahDiproses; + @override void onInit() { super.onInit(); + + // Inisialisasi CounterService jika belum ada + if (!Get.isRegistered()) { + Get.put(CounterService(), permanent: true); + } + _counterService = Get.find(); + loadUserProfile(); loadDashboardData(); loadNotifikasiData(); - loadJadwalHariIni(); + loadJadwalAktif(); } @override @@ -97,6 +116,15 @@ class PetugasDesaDashboardController extends GetxController { await _supabaseService.getTotalSemuaPenyaluran(); totalSemuaPenyaluran.value = semuaPenyaluranData ?? 0; + // Mengambil data status penyaluran + final statusPenyaluranData = await _supabaseService.getStatusPenyaluran(); + if (statusPenyaluranData != null) { + penyaluranDijadwalkan.value = statusPenyaluranData['dijadwalkan'] ?? 0; + penyaluranAktif.value = statusPenyaluranData['aktif'] ?? 0; + penyaluranBatal.value = statusPenyaluranData['batal'] ?? 0; + penyaluranTerlaksana.value = statusPenyaluranData['terlaksana'] ?? 0; + } + // Menghitung progress penyaluran (persentase penyaluran yang terlaksana dari total semua penyaluran) if (totalSemuaPenyaluran.value > 0) { progressPenyaluran.value = @@ -127,9 +155,9 @@ class PetugasDesaDashboardController extends GetxController { } } - Future loadJadwalHariIni() async { + Future loadJadwalAktif() async { try { - final jadwalData = await _supabaseService.getJadwalHariIni(); + final jadwalData = await _supabaseService.getJadwalAktif(); if (jadwalData != null) { jadwalHariIni.value = jadwalData; } @@ -145,7 +173,7 @@ class PetugasDesaDashboardController extends GetxController { loadUserProfile(), loadDashboardData(), loadNotifikasiData(), - loadJadwalHariIni(), + loadJadwalAktif(), ]); } catch (e) { print('Error refreshing data: $e'); diff --git a/lib/app/modules/petugas_desa/controllers/riwayat_stok_controller.dart b/lib/app/modules/petugas_desa/controllers/riwayat_stok_controller.dart index fd162d8..8b497e6 100644 --- a/lib/app/modules/petugas_desa/controllers/riwayat_stok_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/riwayat_stok_controller.dart @@ -302,4 +302,26 @@ class RiwayatStokController extends GetxController { alasan.value = ''; fotoBukti.value = null; } + + // Metode untuk mendapatkan detail referensi berdasarkan id dan sumber + Future?> getReferensiDetail({ + required String idReferensi, + required String sumber, + }) async { + try { + Map? data; + + // Berdasarkan sumber, ambil data dari tabel yang sesuai + if (sumber == 'penitipan') { + data = await _supabaseService.getPenitipanById(idReferensi); + } else if (sumber == 'penerimaan') { + data = await _supabaseService.getPenerimaanById(idReferensi); + } + + return data; + } catch (e) { + print('Error getting referensi detail: $e'); + throw Exception('Gagal mendapatkan data: $e'); + } + } } diff --git a/lib/app/modules/petugas_desa/views/dashboard_view.dart b/lib/app/modules/petugas_desa/views/dashboard_view.dart index cdd7e6c..3d06a02 100644 --- a/lib/app/modules/petugas_desa/views/dashboard_view.dart +++ b/lib/app/modules/petugas_desa/views/dashboard_view.dart @@ -92,7 +92,7 @@ class DashboardView extends GetView { ), const SizedBox(height: 12), FutureBuilder>?>( - future: SupabaseService.to.getJadwalHariIni(), + future: SupabaseService.to.getJadwalAktif(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); @@ -153,11 +153,16 @@ class DashboardView extends GetView { } Widget _buildProgressPenyaluran() { - // Menghitung nilai untuk progress - final terlaksana = controller.totalPenyaluran.value; - final total = controller.totalSemuaPenyaluran.value; - final progressValue = total > 0 ? terlaksana / total : 0.0; - final belumTerlaksana = total - terlaksana; + // Menghitung nilai untuk progress berdasarkan status + final terlaksana = controller.penyaluranTerlaksana.value; + final batal = controller.penyaluranBatal.value; + final dijadwalkan = controller.penyaluranDijadwalkan.value; + final aktif = controller.penyaluranAktif.value; + + final total = terlaksana + batal + dijadwalkan + aktif; + final progressValue = total > 0 ? (terlaksana + batal) / total : 0.0; + final belumTerlaksana = dijadwalkan + + aktif; // Yang belum terlaksana adalah yang dijadwalkan dan aktif return Container( padding: const EdgeInsets.all(16), @@ -214,6 +219,12 @@ class DashboardView extends GetView { Colors.white.withOpacity(0.7), ), const SizedBox(height: 8), + _buildProgressDetailItem( + 'Dibatalkan', + '$batal', + Colors.white.withOpacity(0.7), + ), + const SizedBox(height: 8), _buildProgressDetailItem( 'Total Penyaluran', '$total', @@ -270,18 +281,22 @@ class DashboardView extends GetView { Expanded( child: StatisticCard( title: 'Penitipan', - count: controller.jumlahNotifikasiBelumDibaca.toString(), + count: controller.jumlahMenunggu.value.toString(), subtitle: 'Perlu Konfirmasi', height: 120, icon: Icons.inbox, + gradient: LinearGradient( + colors: [Colors.orange, Colors.deepOrange], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), ), ), const SizedBox(width: 10), Expanded( child: StatisticCard( title: 'Pengaduan', - count: - '${controller.totalPenerima.value > 0 ? controller.totalPenerima.value ~/ 10 : 0}', + count: controller.jumlahDiproses.value.toString(), subtitle: 'Perlu Tindakan', height: 120, gradient: LinearGradient( diff --git a/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart b/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart index 59e57f3..88eed0f 100644 --- a/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart +++ b/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart @@ -698,10 +698,7 @@ class _KonfirmasiPenerimaPageState extends State { try { imageUrl = await controller.uploadBuktiPenerimaan(_buktiPenerimaan!.path); - print('Berhasil upload bukti penerimaan: $imageUrl'); } catch (e) { - // Jika upload bukti penerimaan gagal, tampilkan pesan dan hentikan proses - print('Error upload bukti penerimaan: $e'); throw Exception('Gagal mengupload bukti penerimaan: $e'); } @@ -711,46 +708,31 @@ class _KonfirmasiPenerimaPageState extends State { signatureFile = File('${tempDir.path}/signature.png'); await signatureFile.writeAsBytes(_signatureImage!); - print('Signature file path: ${signatureFile.path}'); - print('Signature file exists: ${signatureFile.existsSync()}'); - print('Signature file size: ${signatureFile.lengthSync()} bytes'); - signatureUrl = await controller.uploadBuktiPenerimaan( signatureFile.path, isTandaTangan: true, ); - print('Berhasil upload tanda tangan: $signatureUrl'); } catch (e) { - // Jika upload tanda tangan gagal, tampilkan pesan dan hentikan proses - print('Error upload tanda tangan: $e'); throw Exception('Gagal mengupload tanda tangan: $e'); } // Konfirmasi penerimaan try { - print('Melakukan konfirmasi penerimaan untuk ID: ${penerima.id}'); await controller.konfirmasiPenerimaan( penerima, buktiPenerimaan: imageUrl, tandaTangan: signatureUrl, ); - print('Konfirmasi penerimaan berhasil'); } catch (e) { - // Jika konfirmasi penerimaan gagal, tampilkan pesan dan hentikan proses - print('Error konfirmasi penerimaan: $e'); throw Exception('Gagal melakukan konfirmasi penerimaan: $e'); } // Hapus file sementara sebelum navigasi - try { - if (signatureFile.existsSync()) { - await signatureFile.delete(); - } - if (tempDir.existsSync()) { - await tempDir.delete(); - } - } catch (e) { - print('Error saat menghapus file sementara: $e'); + if (signatureFile.existsSync()) { + await signatureFile.delete(); + } + if (tempDir.existsSync()) { + await tempDir.delete(); } // Tutup semua snackbar yang mungkin masih terbuka diff --git a/lib/app/modules/petugas_desa/views/pengaduan_view.dart b/lib/app/modules/petugas_desa/views/pengaduan_view.dart index 5c2fd08..8c626a5 100644 --- a/lib/app/modules/petugas_desa/views/pengaduan_view.dart +++ b/lib/app/modules/petugas_desa/views/pengaduan_view.dart @@ -323,144 +323,324 @@ class PengaduanView extends GetView { formattedDate = DateTimeHelper.formatDate(item.tanggalPengaduan); } - return Container( - width: double.infinity, - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration( - color: Colors.white, + return Card( + margin: const EdgeInsets.only(bottom: 16), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withAlpha(26), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], + side: BorderSide( + color: statusColor.withOpacity(0.3), + width: 1, + ), ), - child: Padding( - padding: const EdgeInsets.all(16.0), + elevation: 3, + child: InkWell( + onTap: () { + Get.toNamed('/detail-pengaduan', arguments: {'id': item.id}); + }, + borderRadius: BorderRadius.circular(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - item.warga?['nama'] ?? item.judul ?? '', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - // overflow: TextOverflow.ellipsis, - ), + // Header dengan warna sesuai status + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), ), - const SizedBox(width: 12), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - statusIcon, - size: 16, - color: statusColor, - ), - const SizedBox(width: 4), - Text( - item.status ?? '', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: statusColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Icon( + Icons.report_problem, + color: statusColor, + ), + const SizedBox(width: 8), + Flexible( + child: Text( + item.warga?['nama'] ?? item.judul ?? '', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.bold, + color: statusColor, ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: statusColor, + width: 1.0, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 3, + offset: const Offset(0, 1), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + statusIcon, + size: 14, + color: statusColor, + ), + const SizedBox(width: 4), + Text( + item.status ?? '', + style: TextStyle( + color: statusColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ), + + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Deskripsi masalah + if (item.deskripsi != null && item.deskripsi!.isNotEmpty) + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.grey.shade200, + width: 1.0, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Deskripsi Masalah:', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.grey.shade800, + ), + ), + const SizedBox(height: 6), + Text( + item.deskripsi ?? '', + style: TextStyle( + color: Colors.grey.shade700, + ), + ), + ], + ), + ), + + // Informasi penyaluran bantuan jika ada + if (item.penerimaPenyaluran != null) + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.blue.shade200, + width: 1.0, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Penyaluran: ${item.namaPenyaluran ?? "Tidak tersedia"}', + style: const TextStyle( + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Bantuan', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + Text( + item.stokBantuan?['nama'] ?? '-', + style: TextStyle( + fontWeight: FontWeight.w500, + color: Colors.grey.shade800, + ), + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Jumlah', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + Text( + '${item.jumlahBantuan} ${item.stokBantuan?['satuan'] ?? ''}', + style: TextStyle( + fontWeight: FontWeight.w500, + color: Colors.grey.shade800, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + + // Informasi pelapor dan tanggal + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pelapor', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + Text( + item.warga?['nama_lengkap'] ?? '-', + style: TextStyle( + fontWeight: FontWeight.w500, + color: Colors.grey.shade800, + ), + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'NIK', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + Text( + item.warga?['nik'] ?? '-', + style: TextStyle( + fontWeight: FontWeight.w500, + color: Colors.grey.shade800, + ), + ), + ], + ), ), ], ), - ), - ], - ), - const SizedBox(height: 8), - Text( - item.deskripsi ?? '', - style: Theme.of(context).textTheme.bodyMedium, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _buildItemDetail( - context, - icon: Icons.person, - label: 'Pelapor', - value: item.warga?['nama_lengkap'] ?? '', - ), - ), - Expanded( - child: _buildItemDetail( - context, - icon: Icons.numbers, - label: 'NIK', - value: item.warga?['nik'] ?? '', - ), - ), - ], - ), - const SizedBox(height: 12), - if (item.penerimaPenyaluran != null) ...[ - Row( - children: [ - Expanded( - child: _buildItemDetail( - context, - icon: Icons.shopping_bag, - label: 'Jumlah', - value: - '${item.jumlahBantuan} ${item.stokBantuan['satuan']}', - )), - Expanded( - child: _buildItemDetail( - context, - icon: Icons.inventory, - label: 'Stok Bantuan', - value: item.stokBantuan['nama'] ?? '', + + const SizedBox(height: 12), + + // Informasi tanggal + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(4), ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.calendar_today, + size: 12, + color: Colors.grey.shade700, + ), + const SizedBox(width: 4), + Text( + formattedDate, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade800, + ), + ), + ], + ), + ), + + const SizedBox(height: 12), + + // Tombol detail + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton.icon( + onPressed: () { + Get.toNamed('/detail-pengaduan', + arguments: {'id': item.id}); + }, + icon: const Icon(Icons.info_outline, size: 18), + label: const Text('Lihat Detail'), + style: ElevatedButton.styleFrom( + backgroundColor: statusColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ], ), ], ), - const SizedBox(height: 8), - Row( - children: [ - Expanded( - child: _buildItemDetail( - context, - icon: Icons.category, - label: 'Nama Penyaluran', - value: item.namaPenyaluran ?? '', - ), - ), - Expanded( - child: _buildItemDetail( - context, - icon: Icons.calendar_today, - label: 'Tanggal Pengaduan', - value: formattedDate, - ), - ), - ], - ), - ], - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _buildActionButtons(context, item), ), ], ), @@ -505,174 +685,19 @@ class PengaduanView extends GetView { } List _buildActionButtons(BuildContext context, dynamic item) { - final status = item.status?.toUpperCase(); - - if (status == 'MENUNGGU') { - return [ - TextButton.icon( - onPressed: () { - // Implementasi untuk memproses pengaduan - _showTindakanDialog(context, item); - }, - icon: const Icon(Icons.engineering, size: 18), - label: const Text('Tindakan'), - style: TextButton.styleFrom( - foregroundColor: Colors.blue, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), + return [ + TextButton.icon( + onPressed: () { + // Navigasi ke halaman detail pengaduan + Get.toNamed('/detail-pengaduan', arguments: {'id': item.id}); + }, + icon: const Icon(Icons.info_outline, size: 18), + label: const Text('Detail'), + style: TextButton.styleFrom( + foregroundColor: Colors.grey, + padding: const EdgeInsets.symmetric(horizontal: 8), ), - TextButton.icon( - onPressed: () { - // Navigasi ke halaman detail pengaduan - Get.toNamed('/detail-pengaduan', arguments: {'id': item.id}); - }, - icon: const Icon(Icons.info_outline, size: 18), - label: const Text('Detail'), - style: TextButton.styleFrom( - foregroundColor: Colors.grey, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), - ), - ]; - } else if (status == 'TINDAKAN') { - return [ - TextButton.icon( - onPressed: () { - // Implementasi untuk menyelesaikan pengaduan - _showSelesaikanDialog(context, item); - }, - icon: const Icon(Icons.check_circle, size: 18), - label: const Text('Selesaikan'), - style: TextButton.styleFrom( - foregroundColor: Colors.green, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), - ), - TextButton.icon( - onPressed: () { - // Navigasi ke halaman detail pengaduan - Get.toNamed('/detail-pengaduan', arguments: {'id': item.id}); - }, - icon: const Icon(Icons.info_outline, size: 18), - label: const Text('Detail'), - style: TextButton.styleFrom( - foregroundColor: Colors.grey, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), - ), - ]; - } else { - return [ - TextButton.icon( - onPressed: () { - // Navigasi ke halaman detail pengaduan - Get.toNamed('/detail-pengaduan', arguments: {'id': item.id}); - }, - icon: const Icon(Icons.info_outline, size: 18), - label: const Text('Detail'), - style: TextButton.styleFrom( - foregroundColor: Colors.grey, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), - ), - ]; - } - } - - void _showTindakanDialog(BuildContext context, dynamic item) { - controller.tindakanController.clear(); - controller.catatanController.clear(); - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Tindakan Pengaduan'), - content: Form( - key: controller.tindakanFormKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'), - const SizedBox(height: 16), - TextFormField( - controller: controller.tindakanController, - decoration: const InputDecoration( - labelText: 'Tindakan yang dilakukan', - border: OutlineInputBorder(), - ), - maxLines: 3, - validator: controller.validateTindakan, - ), - const SizedBox(height: 12), - TextFormField( - controller: controller.catatanController, - decoration: const InputDecoration( - labelText: 'Catatan (opsional)', - border: OutlineInputBorder(), - ), - maxLines: 2, - ), - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Batal'), - ), - ElevatedButton( - onPressed: () { - if (controller.tindakanFormKey.currentState!.validate()) { - Navigator.pop(context); - controller.tambahTindakanPengaduan( - pengaduanId: item.id!, - tindakan: controller.tindakanController.text, - kategoriTindakan: 'VERIFIKASI_DATA', - statusTindakan: 'PROSES', - catatan: controller.catatanController.text.isEmpty - ? null - : controller.catatanController.text, - buktiTindakanPaths: [], - ); - } - }, - child: const Text('Simpan'), - ), - ], ), - ); - } - - void _showSelesaikanDialog(BuildContext context, dynamic item) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Selesaikan Pengaduan'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'), - const SizedBox(height: 16), - const Text( - 'Apakah Anda yakin ingin menyelesaikan pengaduan ini?', - textAlign: TextAlign.center, - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Batal'), - ), - ElevatedButton( - onPressed: () { - Navigator.pop(context); - controller.selesaikanPengaduan(item.id!); - }, - child: const Text('Selesaikan'), - ), - ], - ), - ); + ]; } } diff --git a/lib/app/modules/petugas_desa/views/penitipan_view.dart b/lib/app/modules/petugas_desa/views/penitipan_view.dart index 9ae83a0..33e9f63 100644 --- a/lib/app/modules/petugas_desa/views/penitipan_view.dart +++ b/lib/app/modules/petugas_desa/views/penitipan_view.dart @@ -306,236 +306,443 @@ class PenitipanView extends GetView { return Container( width: double.infinity, - margin: const EdgeInsets.only(bottom: 12), + margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.grey.withAlpha(26), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), + color: Colors.grey.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 8, + offset: const Offset(0, 3), ), ], + border: Border.all( + color: statusColor.withOpacity(0.3), + width: 1, + ), ), - child: Padding( - padding: const EdgeInsets.all(16.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row( + // Header dengan status + Container( + color: statusColor.withOpacity(0.1), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ - Expanded( - child: Text( - donaturNama, - style: - Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + statusIcon, + size: 16, + color: statusColor, ), ), - if (isDonaturManual) - Tooltip( - message: 'Donatur Manual (Diinput oleh petugas desa)', - child: Container( - margin: const EdgeInsets.only(left: 4), - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(4), - border: Border.all(color: Colors.blue.shade300), - ), - child: const Text( - 'Manual', - style: TextStyle( - fontSize: 10, - color: Colors.blue, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - ), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - statusIcon, - size: 16, - color: statusColor, - ), - const SizedBox(width: 4), + const SizedBox(width: 8), Text( item.status ?? 'Tidak diketahui', - style: Theme.of(context).textTheme.bodySmall?.copyWith( + style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: statusColor, fontWeight: FontWeight.bold, ), ), ], ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _buildItemDetail( - context, - icon: isUang ? Icons.monetization_on : Icons.category, - label: 'Kategori Bantuan', - value: kategoriNama, - ), - ), - Expanded( - child: _buildItemDetail( - context, - icon: - isUang ? Icons.account_balance_wallet : Icons.inventory, - label: 'Jumlah', - value: isUang - ? 'Rp ${DateTimeHelper.formatNumber(item.jumlah)}' - : '${DateTimeHelper.formatNumber(item.jumlah)} $kategoriSatuan', - ), - ), - ], - ), - const SizedBox(height: 8), - - Row( - children: [ - Expanded( - child: _buildItemDetail( - context, - icon: Icons.calendar_today, - label: 'Tanggal Dibuat', - value: DateTimeHelper.formatDateTime(item.createdAt, - defaultValue: 'Tidak ada tanggal'), - ), - ), - Expanded( - child: item.status == 'TERVERIFIKASI' && - item.petugasDesaId != null - ? _buildItemDetail( - context, - icon: Icons.person, - label: 'Diverifikasi Oleh', - value: - controller.getPetugasDesaNama(item.petugasDesaId), - ) - : const SizedBox(), - ), - ], - ), - - // Tampilkan thumbnail foto bantuan jika ada - if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.photo_library, - size: 16, - color: Colors.grey, - ), - const SizedBox(width: 4), - Text( - 'Foto Bantuan', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.grey, - ), - ), - const SizedBox(width: 4), - Text( - '(${item.fotoBantuan!.length} foto)', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.blue, - fontWeight: FontWeight.bold, - ), - ), - ], + Text( + DateTimeHelper.formatDate(item.createdAt), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey.shade700, + fontStyle: FontStyle.italic, + ), ), ], ), + ), - const SizedBox(height: 12), - if (item.status == 'MENUNGGU') - Row( - mainAxisAlignment: MainAxisAlignment.end, + // Content + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextButton.icon( - onPressed: () { - _showVerifikasiDialog(context, item.id ?? ''); - }, - icon: const Icon(Icons.check, size: 18), - label: const Text('Terima'), - style: TextButton.styleFrom( - foregroundColor: Colors.green, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), + // Donatur info + Row( + children: [ + CircleAvatar( + backgroundColor: AppTheme.primaryColor.withOpacity(0.1), + radius: 20, + child: Text( + donaturNama.substring(0, 1).toUpperCase(), + style: TextStyle( + color: AppTheme.primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + donaturNama, + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith( + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (isDonaturManual) + Container( + margin: const EdgeInsets.only(left: 4), + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: Colors.blue.shade300), + ), + child: const Text( + 'Manual', + style: TextStyle( + fontSize: 10, + color: Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + Text( + 'Donatur', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: Colors.grey, + ), + ), + ], + ), + ), + ], ), - TextButton.icon( - onPressed: () { - _showTolakDialog(context, item.id ?? ''); - }, - icon: const Icon(Icons.close, size: 18), - label: const Text('Tolak'), - style: TextButton.styleFrom( - foregroundColor: Colors.red, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), + + const SizedBox(height: 16), + const Divider(), + const SizedBox(height: 12), + + // Informasi bantuan + Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isUang + ? Colors.green.withOpacity(0.1) + : Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + isUang + ? Icons.monetization_on + : Icons.category, + size: 16, + color: isUang ? Colors.green : Colors.blue, + ), + const SizedBox(width: 6), + Text( + 'Kategori', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: isUang + ? Colors.green + : Colors.blue, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + kategoriNama, + style: Theme.of(context) + .textTheme + .titleSmall + ?.copyWith( + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isUang + ? Colors.amber.withOpacity(0.1) + : Colors.purple.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + isUang + ? Icons.account_balance_wallet + : Icons.inventory, + size: 16, + color: isUang + ? Colors.amber.shade800 + : Colors.purple, + ), + const SizedBox(width: 6), + Text( + 'Jumlah', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: isUang + ? Colors.amber.shade800 + : Colors.purple, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + isUang + ? 'Rp ${DateTimeHelper.formatNumber(item.jumlah)}' + : '${DateTimeHelper.formatNumber(item.jumlah)} $kategoriSatuan', + style: Theme.of(context) + .textTheme + .titleSmall + ?.copyWith( + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + ], ), - TextButton.icon( - onPressed: () { - _showDetailDialog(context, item, donaturNama); - }, - icon: const Icon(Icons.info_outline, size: 18), - label: const Text('Detail'), - style: TextButton.styleFrom( - foregroundColor: Colors.blue, - padding: const EdgeInsets.symmetric(horizontal: 8), + + // Tampilkan thumbnail foto bantuan jika ada + if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 12), + const Divider(), + const SizedBox(height: 10), + Row( + children: [ + Icon( + Icons.photo_library, + size: 16, + color: Colors.grey.shade700, + ), + const SizedBox(width: 6), + Text( + 'Foto Bantuan', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: Colors.grey.shade700, + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${item.fotoBantuan!.length} foto', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ], + ), + + if (item.status == 'TERVERIFIKASI' && + item.petugasDesaId != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 12), + const Divider(), + const SizedBox(height: 10), + Row( + children: [ + Icon( + Icons.verified_user, + size: 16, + color: Colors.green, + ), + const SizedBox(width: 6), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + TextSpan( + text: 'Diverifikasi oleh ', + style: TextStyle( + color: Colors.grey.shade700), + ), + TextSpan( + text: controller.getPetugasDesaNama( + item.petugasDesaId), + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green, + ), + ), + ], + ), + ), + ), + ], + ), + ], ), - ), ], + ), + ), + + // Footer dengan tombol aksi + if (item.status == 'MENUNGGU') + Container( + decoration: BoxDecoration( + color: Colors.grey.shade50, + border: Border( + top: BorderSide(color: Colors.grey.shade200), + ), + ), + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton.icon( + onPressed: () { + _showDetailDialog(context, item, donaturNama); + }, + icon: const Icon(Icons.info_outline, size: 16), + label: const Text('Detail'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue, + side: BorderSide(color: Colors.blue.shade300), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + ), + const SizedBox(width: 8), + OutlinedButton.icon( + onPressed: () { + _showTolakDialog(context, item.id ?? ''); + }, + icon: const Icon(Icons.close, size: 16), + label: const Text('Tolak'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.red, + side: BorderSide(color: Colors.red.shade300), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + ), + const SizedBox(width: 8), + ElevatedButton.icon( + onPressed: () { + _showVerifikasiDialog(context, item.id ?? ''); + }, + icon: const Icon(Icons.check, size: 16), + label: const Text('Terima'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + ), + ], + ), ) else - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - onPressed: () { - _showDetailDialog(context, item, donaturNama); - }, - icon: const Icon(Icons.info_outline, size: 18), - label: const Text('Detail'), - style: TextButton.styleFrom( - foregroundColor: Colors.blue, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), + Container( + decoration: BoxDecoration( + color: Colors.grey.shade50, + border: Border( + top: BorderSide(color: Colors.grey.shade200), ), - ], + ), + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton.icon( + onPressed: () { + _showDetailDialog(context, item, donaturNama); + }, + icon: const Icon(Icons.info_outline, size: 16), + label: const Text('Lihat Detail'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + ), + ), + ], + ), ), ], ), diff --git a/lib/app/modules/petugas_desa/views/penyaluran_view.dart b/lib/app/modules/petugas_desa/views/penyaluran_view.dart index f24c6a3..bd559b7 100644 --- a/lib/app/modules/petugas_desa/views/penyaluran_view.dart +++ b/lib/app/modules/petugas_desa/views/penyaluran_view.dart @@ -75,8 +75,8 @@ class PenyaluranView extends GetView { // Jadwal hari ini JadwalSectionWidget( controller: controller, - title: 'Hari Ini', - jadwalList: controller.jadwalHariIni, + title: 'Penyaluran Aktif', + jadwalList: controller.jadwalAktif, status: 'Aktif', ), @@ -158,7 +158,7 @@ class PenyaluranView extends GetView { context, icon: Icons.event_available, title: 'Aktif', - value: '${controller.jadwalHariIni.length}', + value: '${controller.jadwalAktif.length}', color: Colors.green, )), ), diff --git a/lib/app/modules/petugas_desa/views/petugas_desa_view.dart b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart index a18b461..b2904b5 100644 --- a/lib/app/modules/petugas_desa/views/petugas_desa_view.dart +++ b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart @@ -343,9 +343,6 @@ class PetugasDesaView extends GetView { activeIcon: Icons.warning_amber, title: 'Pengaduan', isSelected: controller.activeTabIndex.value == 3, - badge: controller.jumlahDiproses.value > 0 - ? controller.jumlahDiproses.value.toString() - : null, onTap: () { Navigator.pop(context); controller.changeTab(3); @@ -675,7 +672,7 @@ class PetugasDesaView extends GetView { child: Container( padding: const EdgeInsets.all(2), decoration: const BoxDecoration( - color: Colors.red, + color: Colors.orange, shape: BoxShape.circle, ), constraints: const BoxConstraints( @@ -705,7 +702,7 @@ class PetugasDesaView extends GetView { child: Container( padding: const EdgeInsets.all(2), decoration: const BoxDecoration( - color: Colors.red, + color: Colors.orange, shape: BoxShape.circle, ), constraints: const BoxConstraints( diff --git a/lib/app/modules/petugas_desa/views/riwayat_stok_view.dart b/lib/app/modules/petugas_desa/views/riwayat_stok_view.dart index 6dd26b5..1a524a3 100644 --- a/lib/app/modules/petugas_desa/views/riwayat_stok_view.dart +++ b/lib/app/modules/petugas_desa/views/riwayat_stok_view.dart @@ -15,7 +15,9 @@ class RiwayatStokView extends GetView { return Scaffold( appBar: AppBar( title: const Text('Riwayat Stok Bantuan'), - //back button + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + elevation: 2, leading: IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.arrow_back), @@ -23,6 +25,7 @@ class RiwayatStokView extends GetView { ), body: RefreshIndicator( onRefresh: controller.refreshData, + color: AppTheme.primaryColor, child: Obx(() => controller.isLoading.value ? const Center(child: CircularProgressIndicator()) : _buildContent(context)), @@ -43,6 +46,7 @@ class RiwayatStokView extends GetView { onPressed: () => _showStokManualDialog(context, isAddition: true), backgroundColor: AppTheme.primaryColor, heroTag: 'tambahStok', + elevation: 4, child: const Icon(Icons.add, color: Colors.white), ), ], @@ -53,45 +57,91 @@ class RiwayatStokView extends GetView { Widget _buildContent(BuildContext context) { return SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Heading - Text( - 'Riwayat Stok Bantuan', - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - color: AppTheme.primaryColor, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header dengan latar belakang gradient + Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: AppTheme.primaryGradient, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + spreadRadius: 1, + offset: const Offset(0, 3), + ), + ], ), - const SizedBox(height: 4), - Text( - 'Catatan riwayat perubahan stok bantuan', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.grey[600], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Heading + Row( + children: [ + const Icon( + Icons.inventory_2_outlined, + color: Colors.white, + size: 30, + ), + const SizedBox(width: 10), + Text( + 'Riwayat Stok Bantuan', + style: + Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.only(left: 40), + child: Text( + 'Catatan perubahan stok bantuan di desa Anda', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white.withOpacity(0.9), + ), ), + ), + ], ), - const SizedBox(height: 16), + ), - // Filter dan pencarian - _buildFilters(context), - const SizedBox(height: 16), + // Filter dan pencarian + Padding( + padding: const EdgeInsets.all(16.0), + child: _buildFilters(context), + ), - // Daftar riwayat stok - Obx(() { + // Daftar riwayat stok + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Obx(() { final filteredRiwayat = controller.getFilteredRiwayatStok(); if (filteredRiwayat.isEmpty) { return Center( child: Padding( - padding: const EdgeInsets.all(32.0), + padding: const EdgeInsets.symmetric(vertical: 50.0), child: Column( children: [ - const Icon( - Icons.history, - size: 64, - color: Colors.grey, + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.history, + size: 64, + color: Colors.grey, + ), ), const SizedBox(height: 16), Text( @@ -99,6 +149,16 @@ class RiwayatStokView extends GetView { style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Colors.grey[600], + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'Gunakan tombol + untuk menambah stok bantuan', + style: + Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.grey[500], ), textAlign: TextAlign.center, ), @@ -107,134 +167,262 @@ class RiwayatStokView extends GetView { ), ); } - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: filteredRiwayat.length, - itemBuilder: (context, index) { - final riwayat = filteredRiwayat[index]; - return _buildRiwayatItem(context, riwayat); - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + Icons.history, + color: AppTheme.primaryColor, + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Daftar Riwayat', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppTheme.primaryColor, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + filteredRiwayat.length.toString(), + style: TextStyle( + color: AppTheme.primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: filteredRiwayat.length, + itemBuilder: (context, index) { + final riwayat = filteredRiwayat[index]; + return _buildRiwayatItem(context, riwayat); + }, + ), + ], ); }), - ], - ), + ), + const SizedBox(height: 80), // Ruang untuk floating action button + ], ), ); } Widget _buildFilters(BuildContext context) { - return Column( - children: [ - // Pencarian - Container( - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + spreadRadius: 0, + offset: const Offset(0, 2), ), - child: TextField( - controller: controller.searchController, - decoration: const InputDecoration( - hintText: 'Cari riwayat stok...', - prefixIcon: Icon(Icons.search), - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 12), - ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Judul Filter + Row( + children: [ + const Icon( + Icons.filter_list, + color: AppTheme.primaryColor, + ), + const SizedBox(width: 8), + Text( + 'Filter Riwayat', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppTheme.primaryColor, + ), + ), + ], ), - ), - const SizedBox(height: 16), + const SizedBox(height: 16), - // Filter jenis perubahan - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Jenis Perubahan'), - const SizedBox(height: 4), - Container( - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), - ), - child: Obx(() => DropdownButtonFormField( - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 12), - ), - isExpanded: true, - value: controller.filterJenisPerubahan.value, - items: [ - const DropdownMenuItem( - value: 'semua', - child: Text('Semua'), - ), - const DropdownMenuItem( - value: 'penambahan', - child: Text('Penambahan'), - ), - const DropdownMenuItem( - value: 'pengurangan', - child: Text('Pengurangan'), - ), - ], - onChanged: (value) { - if (value != null) { - controller.filterByJenisPerubahan(value); - } - }, - )), - ), - ], + // Pencarian + Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: TextField( + controller: controller.searchController, + decoration: InputDecoration( + hintText: 'Cari riwayat stok...', + prefixIcon: Icon(Icons.search, + color: AppTheme.primaryColor.withOpacity(0.7)), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(vertical: 12), ), ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Jenis Bantuan'), - const SizedBox(height: 4), - Container( - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), + ), + const SizedBox(height: 16), + + // Filter jenis perubahan dan jenis bantuan + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Jenis Perubahan', + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.grey[800], + ), ), - child: Obx(() => DropdownButtonFormField( - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 12), - ), - isExpanded: true, - value: controller.filterStokBantuanId.value, - items: [ - const DropdownMenuItem( - value: 'semua', - child: Text('Semua'), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: Obx(() => DropdownButtonFormField( + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 12), ), - ...controller.daftarStokBantuan.map((stok) { - return DropdownMenuItem( - value: stok.id, - child: Text(stok.nama ?? '-'), - ); - }).toList(), - ], - onChanged: (value) { - if (value != null) { - controller.filterByStokBantuan(value); - } - }, - )), - ), - ], + isExpanded: true, + icon: Icon(Icons.arrow_drop_down, + color: AppTheme.primaryColor), + value: controller.filterJenisPerubahan.value, + items: [ + DropdownMenuItem( + value: 'semua', + child: Row( + children: [ + Icon(Icons.all_inclusive, + size: 18, color: Colors.grey[600]), + const SizedBox(width: 8), + const Text('Semua'), + ], + ), + ), + DropdownMenuItem( + value: 'penambahan', + child: Row( + children: [ + const Icon(Icons.add_circle_outline, + size: 18, color: Colors.green), + const SizedBox(width: 8), + const Text('Penambahan'), + ], + ), + ), + DropdownMenuItem( + value: 'pengurangan', + child: Row( + children: [ + const Icon(Icons.remove_circle_outline, + size: 18, color: Colors.red), + const SizedBox(width: 8), + const Text('Pengurangan'), + ], + ), + ), + ], + onChanged: (value) { + if (value != null) { + controller.filterByJenisPerubahan(value); + } + }, + )), + ), + ], + ), ), - ), - ], - ), - ], + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Jenis Bantuan', + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.grey[800], + ), + ), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: Obx(() => DropdownButtonFormField( + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 12), + ), + isExpanded: true, + icon: Icon(Icons.arrow_drop_down, + color: AppTheme.primaryColor), + value: controller.filterStokBantuanId.value, + items: [ + DropdownMenuItem( + value: 'semua', + child: Row( + children: [ + Icon(Icons.category_outlined, + size: 18, color: Colors.grey[600]), + const SizedBox(width: 8), + const Text('Semua'), + ], + ), + ), + ...controller.daftarStokBantuan.map((stok) { + return DropdownMenuItem( + value: stok.id, + child: Text( + stok.nama ?? '-', + overflow: TextOverflow.ellipsis, + ), + ); + }).toList(), + ], + onChanged: (value) { + if (value != null) { + controller.filterByStokBantuan(value); + } + }, + )), + ), + ], + ), + ), + ], + ), + ], + ), ); } @@ -247,148 +435,324 @@ class RiwayatStokView extends GetView { riwayat.stokBantuan != null ? riwayat.stokBantuan!['satuan'] ?? '' : ''; final sumberLabels = { 'penitipan': 'Penitipan', - 'penyaluran': 'Penyaluran', + 'penerimaan': 'Penerimaan', 'manual': 'Manual', }; final sumberLabel = sumberLabels[riwayat.sumber] ?? 'Tidak diketahui'; + final sumberIcons = { + 'penitipan': Icons.inventory, + 'penerimaan': Icons.local_shipping, + 'manual': Icons.edit, + }; + final sumberIcon = sumberIcons[riwayat.sumber] ?? Icons.help_outline; + final sumberColors = { + 'penitipan': Colors.blue, + 'penerimaan': Colors.purple, + 'manual': Colors.orange, + }; + final sumberColor = sumberColors[riwayat.sumber] ?? Colors.grey; - return Card( - margin: const EdgeInsets.only(bottom: 12), - elevation: 2, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header: Jumlah dan waktu - Row( - children: [ - // Icon & Jumlah - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: isPenambahan ? Colors.green[100] : Colors.red[100], - borderRadius: BorderRadius.circular(16), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - isPenambahan ? Icons.add : Icons.remove, - color: isPenambahan ? Colors.green : Colors.red, - size: 14, - ), - const SizedBox(width: 4), - Text( - '${riwayat.jumlah?.toStringAsFixed(0) ?? '0'} $stokBantuanSatuan', - style: TextStyle( - fontWeight: FontWeight.bold, - color: isPenambahan ? Colors.green : Colors.red, - ), - ), - ], + // Cek apakah memiliki id_referensi dan bukan dari sumber manual + final bool hasSumberReferensi = riwayat.idReferensi != null && + riwayat.sumber != 'manual' && + riwayat.idReferensi!.isNotEmpty; + + return Container( + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 8, + spreadRadius: 0, + offset: const Offset(0, 2), + ), + ], + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(12), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: hasSumberReferensi + ? () => _showReferensiDetailDialog(context, riwayat) + : null, + child: Column( + children: [ + // Header dengan warna sesuai jenis perubahan + Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + color: + isPenambahan ? Colors.green.shade50 : Colors.red.shade50, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), ), ), - const Spacer(), - // Sumber - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(16), - ), - child: Text( - sumberLabel, - style: TextStyle( - color: Colors.grey[700], - fontSize: 12, - ), - ), - ), - const SizedBox(width: 8), - // Tanggal - Text( - riwayat.createdAt != null - ? DateTimeHelper.formatDateTime(riwayat.createdAt!) - : '-', - style: TextStyle( - color: Colors.grey[600], - fontSize: 12, - ), - ), - ], - ), - const SizedBox(height: 12), - - // Nama bantuan - Text( - stokBantuanNama, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - - // Alasan jika ada - if (riwayat.alasan != null && riwayat.alasan!.isNotEmpty) ...[ - const SizedBox(height: 8), - Text( - 'Alasan: ${riwayat.alasan}', - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - - // Foto bukti jika ada - if (riwayat.fotoBukti != null && riwayat.fotoBukti!.isNotEmpty) ...[ - const SizedBox(height: 8), - InkWell( - onTap: () => _showImageDialog(context, riwayat.fotoBukti!), child: Row( children: [ - const Icon( - Icons.photo, - color: Colors.blue, - size: 20, + // Status dan jumlah + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: isPenambahan + ? Colors.green.withOpacity(0.15) + : Colors.red.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isPenambahan ? Icons.add : Icons.remove, + color: isPenambahan ? Colors.green : Colors.red, + size: 16, + ), + const SizedBox(width: 4), + Text( + '${riwayat.jumlah?.toStringAsFixed(0) ?? '0'} $stokBantuanSatuan', + style: TextStyle( + fontWeight: FontWeight.bold, + color: isPenambahan ? Colors.green : Colors.red, + ), + ), + ], + ), ), - const SizedBox(width: 4), - Text( - 'Lihat Bukti', - style: TextStyle( - color: Colors.blue, + const Spacer(), + // Sumber + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: sumberColor.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + sumberIcon, + size: 14, + color: sumberColor, + ), + const SizedBox(width: 4), + Text( + sumberLabel, + style: TextStyle( + color: sumberColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], ), ), ], ), ), - ], - // Petugas - if (riwayat.createdBy != null) ...[ - const SizedBox(height: 12), - Row( - children: [ - const Icon( - Icons.person, - size: 16, - color: Colors.grey, - ), - const SizedBox(width: 4), - Text( - 'Oleh: ${riwayat.createdBy!['nama_lengkap'] ?? 'Tidak diketahui'}', - style: TextStyle( - color: Colors.grey[600], - fontSize: 12, + // Konten + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Nama bantuan + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.category, + color: AppTheme.primaryColor, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + stokBantuanNama, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + riwayat.createdAt != null + ? DateTimeHelper.formatDateTime( + riwayat.createdAt!) + : '-', + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), + ), + ], ), - ), - ], + + // Alasan jika ada + if (riwayat.alasan != null && + riwayat.alasan!.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.only(left: 44), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.description_outlined, + size: 16, + color: Colors.grey[600], + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Alasan: ${riwayat.alasan}', + style: TextStyle( + color: Colors.grey[700], + ), + ), + ), + ], + ), + ], + ), + ), + ], + + // Foto bukti jika ada + if (riwayat.fotoBukti != null && + riwayat.fotoBukti!.isNotEmpty) ...[ + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(left: 44), + child: InkWell( + onTap: () => + _showImageDialog(context, riwayat.fotoBukti!), + child: Container( + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.photo, + color: Colors.blue, + size: 18, + ), + const SizedBox(width: 6), + const Text( + 'Lihat Bukti', + style: TextStyle( + color: Colors.blue, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ), + ], + + // Info referensi jika ada + if (hasSumberReferensi) ...[ + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(left: 44), + child: Container( + decoration: BoxDecoration( + color: sumberColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + riwayat.sumber == 'penitipan' + ? Icons.inventory + : Icons.local_shipping, + size: 18, + color: sumberColor, + ), + const SizedBox(width: 6), + Text( + 'Lihat detail ${riwayat.sumber}', + style: TextStyle( + color: sumberColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], + + // Petugas + if (riwayat.createdBy != null) ...[ + const Divider(height: 24), + Row( + children: [ + CircleAvatar( + radius: 14, + backgroundColor: Colors.grey[200], + child: Icon( + Icons.person, + size: 18, + color: Colors.grey[700], + ), + ), + const SizedBox(width: 8), + Text( + 'Oleh: ${riwayat.createdBy!['nama_lengkap'] ?? 'Tidak diketahui'}', + style: TextStyle( + color: Colors.grey[700], + fontSize: 13, + ), + ), + ], + ), + ], + ], + ), ), ], - ], + ), ), ), ); @@ -400,37 +764,82 @@ class RiwayatStokView extends GetView { builder: (BuildContext context) { return Dialog( insetPadding: const EdgeInsets.all(16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), child: Column( mainAxisSize: MainAxisSize.min, children: [ AppBar( leading: IconButton( - icon: const Icon(Icons.close), + icon: const Icon( + Icons.close, + color: Colors.white, + ), onPressed: () => Navigator.of(context).pop(), ), - title: const Text('Bukti Foto'), + title: const Text( + 'Bukti Foto', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), elevation: 0, backgroundColor: AppTheme.primaryColor, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), ), - InteractiveViewer( - panEnabled: true, - boundaryMargin: const EdgeInsets.all(16), - minScale: 0.5, - maxScale: 4, - child: CachedNetworkImage( - imageUrl: imageUrl, - placeholder: (context, url) => const Center( - child: CircularProgressIndicator(), + SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: InteractiveViewer( + panEnabled: true, + boundaryMargin: const EdgeInsets.all(16), + minScale: 0.5, + maxScale: 4, + child: CachedNetworkImage( + imageUrl: imageUrl, + placeholder: (context, url) => const Center( + child: CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.error, color: Colors.red, size: 48), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Gagal memuat gambar: $error', + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.red), + ), + ), + ], + ), + fit: BoxFit.contain, ), - errorWidget: (context, url, error) => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error), - const SizedBox(height: 8), - Text('Gagal memuat gambar: $error'), - ], - ), - fit: BoxFit.contain, + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.zoom_in, size: 20, color: Colors.grey), + const SizedBox(width: 8), + Text( + 'Cubit untuk memperbesar/memperkecil', + style: TextStyle( + color: Colors.grey[600], + fontSize: 14, + ), + ), + ], ), ), ], @@ -685,4 +1094,688 @@ class RiwayatStokView extends GetView { }, ); } + + // Tambahkan metode baru untuk menampilkan dialog detail referensi + void _showReferensiDetailDialog( + BuildContext context, RiwayatStokModel riwayat) { + showDialog( + context: context, + builder: (BuildContext context) { + return Dialog( + insetPadding: const EdgeInsets.all(16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: FutureBuilder?>( + future: controller.getReferensiDetail( + idReferensi: riwayat.idReferensi!, sumber: riwayat.sumber!), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const SizedBox( + height: 200, + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + + if (snapshot.hasError) { + return Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.error_outline, + color: Colors.red, + size: 48, + ), + const SizedBox(height: 16), + Text( + 'Terjadi kesalahan', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 8), + Text( + 'Tidak dapat memuat data: ${snapshot.error}', + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Tutup'), + ), + ], + ), + ); + } + + final data = snapshot.data; + if (data == null) { + return Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.info_outline, + color: Colors.amber, + size: 48, + ), + const SizedBox(height: 16), + Text( + 'Data Tidak Ditemukan', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 8), + const Text( + 'Detail data tidak tersedia atau mungkin sudah dihapus', + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Tutup'), + ), + ], + ), + ); + } + + // Tampilkan detail sesuai dengan jenis sumber + if (riwayat.sumber == 'penitipan') { + return _buildPenitipanDetail(context, data); + } else if (riwayat.sumber == 'penerimaan') { + return _buildPenerimaanDetail(context, data); + } else { + return const SizedBox( + height: 100, + child: Center( + child: Text('Tipe data tidak dikenal'), + ), + ); + } + }, + ), + ); + }, + ); + } + + // Widget untuk menampilkan detail penitipan + Widget _buildPenitipanDetail( + BuildContext context, Map data) { + final String tanggal = data['created_at'] != null + ? DateTimeHelper.formatDateTime(DateTime.parse(data['created_at'])) + : '-'; + + final String namaPenitip = data['donatur'] != null + ? data['donatur']['nama_lengkap'] ?? 'Tidak diketahui' + : 'Tidak diketahui'; + + final String namaPetugas = data['petugas_desa'] != null + ? data['petugas_desa']['nama_lengkap'] ?? 'Tidak diketahui' + : 'Tidak diketahui'; + + final List fotoBantuan = data['foto_bantuan'] ?? []; + + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header dengan desain yang lebih menarik + Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + gradient: AppTheme.primaryGradient, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + const Icon( + Icons.inventory, + color: Colors.white, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Detail Penitipan', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + + const SizedBox(height: 20), + + // Informasi detail dengan desain yang lebih menarik + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.info_outline, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'ID Penitipan: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: data['id'] ?? '-'), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + const Icon(Icons.calendar_today, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'Tanggal: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: tanggal), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + const Icon(Icons.person, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'Penitip: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: namaPenitip), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + const Icon(Icons.admin_panel_settings, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'Petugas: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: namaPetugas), + ], + ), + ), + ), + ], + ), + ], + ), + ), + + const SizedBox(height: 20), + + // Foto Bukti jika ada dengan desain yang lebih menarik + if (fotoBantuan.isNotEmpty) ...[ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.photo, color: AppTheme.primaryColor), + const SizedBox(width: 8), + const Text( + 'Foto Bukti Penitipan', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ], + ), + const SizedBox(height: 12), + SizedBox( + height: 180, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: fotoBantuan.length, + itemBuilder: (context, index) { + final String imageUrl = fotoBantuan[index]; + return Padding( + padding: EdgeInsets.only( + right: index < fotoBantuan.length - 1 ? 8.0 : 0), + child: InkWell( + onTap: () => _showImageDialog(context, imageUrl), + child: Container( + width: 200, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 3, + offset: const Offset(0, 1), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + placeholder: (context, url) => const Center( + child: CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + Container( + color: Colors.grey[300], + child: const Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon(Icons.error, + color: Colors.red, size: 32), + SizedBox(height: 8), + Text('Gagal memuat gambar'), + ], + ), + ), + ), + ), + ), + ), + ); + }, + ), + ), + const SizedBox(height: 8), + const Center( + child: Text( + 'Ketuk untuk memperbesar', + style: TextStyle( + fontSize: 12, + color: Colors.grey, + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + ], + + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text('Tutup', style: TextStyle(fontSize: 16)), + ), + ), + ], + ), + ); + } + + // Widget untuk menampilkan detail penerimaan + Widget _buildPenerimaanDetail( + BuildContext context, Map data) { + final String tanggal = data['created_at'] != null + ? DateTimeHelper.formatDateTime(DateTime.parse(data['created_at'])) + : '-'; + + final String namaPenerima = data['warga'] != null + ? data['warga']['nama_lengkap'] ?? 'Tidak diketahui' + : 'Tidak diketahui'; + + final String namaPetugas = + data['penyaluran_bantuan']['petugas_desa'] != null + ? data['penyaluran_bantuan']['petugas_desa']['nama_lengkap'] ?? + 'Tidak diketahui' + : 'Tidak diketahui'; + + final String buktiPenerimaan = data['bukti_penerimaan'] ?? ''; + + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header dengan desain yang lebih menarik + Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + gradient: AppTheme.primaryGradient, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + const Icon( + Icons.local_shipping, + color: Colors.white, + size: 28, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Detail Penerimaan', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + const SizedBox(height: 16), + + // Informasi dalam card + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // ID Penerimaan dengan ikon + Row( + children: [ + const Icon(Icons.tag, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'ID Penerimaan: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: data['id'] ?? '-'), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 12), + + // Tanggal dengan ikon + Row( + children: [ + const Icon(Icons.calendar_today, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'Tanggal: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: tanggal), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 12), + + // Penerima dengan ikon + Row( + children: [ + const Icon(Icons.person, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'Penerima: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: namaPenerima), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 12), + + // Petugas dengan ikon + Row( + children: [ + const Icon(Icons.admin_panel_settings, + size: 18, color: AppTheme.primaryColor), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + const TextSpan( + text: 'Petugas: ', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: namaPetugas), + ], + ), + ), + ), + ], + ), + ], + ), + ), + + const SizedBox(height: 20), + + // Foto Bukti jika ada dengan desain yang lebih menarik + if (buktiPenerimaan.isNotEmpty) ...[ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.photo, color: AppTheme.primaryColor), + const SizedBox(width: 8), + const Text( + 'Foto Bukti Penerimaan', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ], + ), + const SizedBox(height: 12), + InkWell( + onTap: () => _showImageDialog(context, buktiPenerimaan), + child: Container( + height: 180, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 3, + offset: const Offset(0, 1), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: buktiPenerimaan, + fit: BoxFit.cover, + placeholder: (context, url) => const Center( + child: CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => Container( + color: Colors.grey[300], + child: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error, color: Colors.red, size: 32), + SizedBox(height: 8), + Text('Gagal memuat gambar'), + ], + ), + ), + ), + ), + ), + ), + const SizedBox(height: 8), + const Center( + child: Text( + 'Ketuk untuk memperbesar', + style: TextStyle( + fontSize: 12, + color: Colors.grey, + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + ], + + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + icon: const Icon(Icons.check_circle), + label: const Text( + 'Tutup', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ); + } } diff --git a/lib/app/modules/petugas_desa/views/stok_bantuan_view.dart b/lib/app/modules/petugas_desa/views/stok_bantuan_view.dart index 15f8a84..64fdb3f 100644 --- a/lib/app/modules/petugas_desa/views/stok_bantuan_view.dart +++ b/lib/app/modules/petugas_desa/views/stok_bantuan_view.dart @@ -332,176 +332,283 @@ class StokBantuanView extends GetView { } Widget _buildStokBantuanItem(BuildContext context, StokBantuanModel item) { + // Tentukan warna berdasarkan jenis bantuan + Color categoryColor = + item.isUang == true ? Colors.amber.shade700 : AppTheme.primaryColor; + + // Cek apakah stok hampir habis (kurang dari 10) + bool isLowStock = !item.isUang! && item.totalStok! < 10; + return Container( width: double.infinity, - margin: const EdgeInsets.only(bottom: 12), + margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.grey.withAlpha(26), + color: Colors.grey.withAlpha(30), spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), + blurRadius: 6, + offset: const Offset(0, 2), ), ], ), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - item.nama ?? 'Tanpa Nama', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header dengan gradient berdasarkan jenis bantuan + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + categoryColor.withOpacity(0.8), + categoryColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Row( + children: [ + Expanded( + child: Text( + item.nama ?? 'Tanpa Nama', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(30), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + item.isUang == true + ? Icons.monetization_on + : Icons.inventory_2_outlined, + size: 14, + color: Colors.white, ), - overflow: TextOverflow.ellipsis, + const SizedBox(width: 4), + Text( + item.kategoriBantuan != null + ? (item.kategoriBantuan!['nama'] ?? + 'Tidak Ada Kategori') + : 'Tidak Ada Kategori', + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + ), + + // Body content + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Deskripsi + if (item.deskripsi != null && item.deskripsi!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + item.deskripsi!, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.grey[700], + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + + // Detail stok/dana dalam card + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isLowStock + ? Colors.red.shade50 + : (item.isUang == true + ? Colors.amber.shade50 + : Colors.blue.shade50), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: isLowStock + ? Colors.red.shade200 + : (item.isUang == true + ? Colors.amber.shade200 + : Colors.blue.shade200), + shape: BoxShape.circle, + ), + child: Icon( + item.isUang == true + ? Icons.monetization_on + : (isLowStock + ? Icons.warning_amber_rounded + : Icons.inventory), + size: 20, + color: isLowStock + ? Colors.red.shade800 + : (item.isUang == true + ? Colors.amber.shade800 + : Colors.blue.shade800), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.isUang == true + ? 'Total Dana' + : (isLowStock + ? 'Stok Hampir Habis!' + : 'Total Stok'), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + fontWeight: FontWeight.w500, + color: isLowStock + ? Colors.red.shade800 + : (item.isUang == true + ? Colors.amber.shade800 + : Colors.blue.shade800), + ), + ), + Text( + item.isUang == true + ? 'Rp ${DateTimeHelper.formatNumber(item.totalStok)}' + : '${DateTimeHelper.formatNumber(item.totalStok)} ${item.satuan ?? ''}', + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith( + fontWeight: FontWeight.bold, + color: isLowStock + ? Colors.red.shade900 + : (item.isUang == true + ? Colors.amber.shade900 + : Colors.blue.shade900), + ), + ), + ], + ), + ), + ], + ), + ], ), ), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (item.isUang == true) - const Icon( - Icons.monetization_on, - size: 16, - color: AppTheme.primaryColor, - ), - if (item.isUang == true) const SizedBox(width: 4), - Text( - item.kategoriBantuan != null - ? (item.kategoriBantuan!['nama'] ?? - 'Tidak Ada Kategori') - : 'Tidak Ada Kategori', + + const SizedBox(height: 16), + + // Additional details + Row( + children: [ + Icon( + Icons.access_time, + size: 16, + color: Colors.grey[600], + ), + const SizedBox(width: 4), + Expanded( + child: Text( + item.updatedAt != null + ? 'Diperbarui: ${DateTimeHelper.formatDateTimeWithHour(item.updatedAt!)}' + : 'Tidak ada data pembaruan', style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppTheme.primaryColor, - fontWeight: FontWeight.bold, + color: Colors.grey[600], ), ), + ), + ], + ), + + const SizedBox(height: 12), + + // Tombol Aksi + Container( + decoration: BoxDecoration( + color: Colors.grey.shade50, + border: Border( + top: BorderSide(color: Colors.grey.shade200), + ), + ), + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton.icon( + onPressed: () { + // Tampilkan dialog edit stok bantuan + _showEditStokDialog(context, item); + }, + icon: const Icon(Icons.edit_outlined, size: 16), + label: const Text('Edit'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue, + side: BorderSide(color: Colors.blue.shade300), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + ), + const SizedBox(width: 8), + OutlinedButton.icon( + onPressed: () { + // Tampilkan dialog konfirmasi hapus + _showDeleteConfirmation(context, item); + }, + icon: const Icon(Icons.delete_outline, size: 16), + label: const Text('Hapus'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.red, + side: BorderSide(color: Colors.red.shade300), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + ), ], ), ), ], ), - if (item.deskripsi != null && item.deskripsi!.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - item.deskripsi!, - style: Theme.of(context).textTheme.bodySmall, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _buildItemDetail( - context, - icon: item.isUang == true - ? Icons.monetization_on - : Icons.inventory, - label: item.isUang == true ? 'Total Dana' : 'Total Stok', - value: item.isUang == true - ? 'Rp ${DateTimeHelper.formatNumber(item.totalStok)}' - : '${DateTimeHelper.formatNumber(item.totalStok)} ${item.satuan ?? ''}', - ), - ), - Expanded( - child: _buildItemDetail( - context, - icon: Icons.access_time, - label: 'Terakhir Diperbarui', - value: item.updatedAt != null - ? '${item.updatedAt!.day}/${item.updatedAt!.month}/${item.updatedAt!.year} ${item.updatedAt!.hour}:${item.updatedAt!.minute}' - : 'Tidak ada data', - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - onPressed: () { - // Tampilkan dialog edit stok bantuan - _showEditStokDialog(context, item); - }, - icon: const Icon(Icons.edit_outlined, size: 18), - label: const Text('Edit'), - style: TextButton.styleFrom( - foregroundColor: Colors.blue, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), - ), - TextButton.icon( - onPressed: () { - // Tampilkan dialog konfirmasi hapus - _showDeleteConfirmation(context, item); - }, - icon: const Icon(Icons.delete_outline, size: 18), - label: const Text('Hapus'), - style: TextButton.styleFrom( - foregroundColor: Colors.red, - padding: const EdgeInsets.symmetric(horizontal: 8), - ), - ), - ], - ), - ], - ), - ), - ); - } - - Widget _buildItemDetail( - BuildContext context, { - required IconData icon, - required String label, - required String value, - }) { - return Row( - children: [ - Icon( - icon, - size: 16, - color: Colors.grey, - ), - const SizedBox(width: 4), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.grey, - ), - ), - Text( - value, - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.ellipsis, - ), - ], ), - ), - ], + ], + ), ); } diff --git a/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart b/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart index f2c9d58..701ee0d 100644 --- a/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart +++ b/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart @@ -129,7 +129,7 @@ class TambahPenyaluranView extends GetView { fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 24), + const SizedBox(height: 16), // Skema Bantuan Text( @@ -175,6 +175,18 @@ class TambahPenyaluranView extends GetView { } await loadPengajuanKelayakan(value); + + // Periksa apakah ada penerima + if (jumlahPenerima.value == 0) { + Get.snackbar( + 'Perhatian', + 'Skema bantuan ini tidak memiliki penerima yang terverifikasi!', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 4), + ); + } } }, validator: (value) { @@ -184,6 +196,37 @@ class TambahPenyaluranView extends GetView { return null; }, )), + + // const SizedBox(height: 16), + // Pesan pemberitahuan jika tidak ada penerima + Obx(() => jumlahPenerima.value == 0 && + selectedSkemaBantuanId.value != null + ? Container( + margin: const EdgeInsets.only(top: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.red.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.red.shade200), + ), + child: Row( + children: [ + Icon(Icons.warning_amber_rounded, + color: Colors.red.shade700), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Skema bantuan ini tidak memiliki penerima yang terverifikasi. Tambahkan penerima terlebih dahulu.', + style: TextStyle( + color: Colors.red.shade700, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ) + : const SizedBox()), const SizedBox(height: 16), // Jumlah Penerima (Otomatis) Row( @@ -755,67 +798,78 @@ class TambahPenyaluranView extends GetView { // Tombol Submit SizedBox( width: double.infinity, - child: ElevatedButton( - onPressed: () { - if (formKey.currentState!.validate()) { - // Periksa kecukupan stok - if (!isStokCukup.value) { - Get.snackbar( - 'Stok Tidak Cukup', - 'Stok bantuan tidak mencukupi untuk penyaluran ini. Silakan tambah stok terlebih dahulu.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 4), - ); - return; - } + child: Obx(() => ElevatedButton( + onPressed: jumlahPenerima.value > 0 + ? () { + if (formKey.currentState!.validate()) { + // Periksa kecukupan stok + if (!isStokCukup.value) { + Get.snackbar( + 'Stok Tidak Cukup', + 'Stok bantuan tidak mencukupi untuk penyaluran ini. Silakan tambah stok terlebih dahulu.', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 4), + ); + return; + } - // Gabungkan tanggal dan waktu mulai - DateTime? tanggalWaktuMulai; - if (selectedDate.value != null && - selectedWaktuMulai.value != null) { - tanggalWaktuMulai = DateTime( - selectedDate.value!.year, - selectedDate.value!.month, - selectedDate.value!.day, - selectedWaktuMulai.value!.hour, - selectedWaktuMulai.value!.minute, - ).toLocal(); - } + // Gabungkan tanggal dan waktu mulai + DateTime? tanggalWaktuMulai; + if (selectedDate.value != null && + selectedWaktuMulai.value != null) { + tanggalWaktuMulai = DateTime( + selectedDate.value!.year, + selectedDate.value!.month, + selectedDate.value!.day, + selectedWaktuMulai.value!.hour, + selectedWaktuMulai.value!.minute, + ).toLocal(); + } - // Panggil fungsi untuk menambahkan penyaluran - controller.tambahPenyaluran( - nama: namaController.text, - deskripsi: deskripsiController.text, - skemaId: selectedSkemaBantuanId.value!, - lokasiPenyaluranId: selectedLokasiPenyaluranId.value!, - jumlahPenerima: jumlahPenerima.value, - tanggalPenyaluran: tanggalWaktuMulai, - kategoriBantuanId: - selectedSkemaBantuan.value!.kategoriBantuanId!, - jumlahDiterimaPerOrang: jumlahDiterimaPerOrang.value, - stokBantuanId: - selectedSkemaBantuan.value!.stokBantuanId!, - totalStokDibutuhkan: totalStokDibutuhkan.value); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.primaryColor, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: const Text( - 'Simpan Penyaluran', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), + // Panggil fungsi untuk menambahkan penyaluran + controller.tambahPenyaluran( + nama: namaController.text, + deskripsi: deskripsiController.text, + skemaId: selectedSkemaBantuanId.value!, + lokasiPenyaluranId: + selectedLokasiPenyaluranId.value!, + jumlahPenerima: jumlahPenerima.value, + tanggalPenyaluran: tanggalWaktuMulai, + kategoriBantuanId: selectedSkemaBantuan + .value!.kategoriBantuanId!, + jumlahDiterimaPerOrang: + jumlahDiterimaPerOrang.value, + stokBantuanId: selectedSkemaBantuan + .value!.stokBantuanId!, + totalStokDibutuhkan: + totalStokDibutuhkan.value); + + //get back and refresh page + Get.back(); + controller.refreshData(); + } + } + : null, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + disabledBackgroundColor: Colors.grey.shade300, + disabledForegroundColor: Colors.grey.shade600, + ), + child: const Text( + 'Simpan Penyaluran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + )), ), ], ), diff --git a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart index 3112fa2..bd0323b 100644 --- a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart +++ b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart @@ -44,6 +44,12 @@ class WargaDashboardController extends GetxController { // Jumlah notifikasi belum dibaca final RxInt jumlahNotifikasiBelumDibaca = 0.obs; + // Variabel untuk mengontrol auto-refresh + final RxBool _autoRefreshEnabled = true.obs; + + // Getter untuk status auto-refresh + bool get isAutoRefreshEnabled => _autoRefreshEnabled.value; + // Getter untuk data user BaseUserModel? get user => _authController.baseUser; String get role => user?.role ?? 'WARGA'; @@ -74,6 +80,14 @@ class WargaDashboardController extends GetxController { return null; } + // Getter untuk NIK + String? get nik { + if (_authController.isWarga && _authController.roleData != null) { + return (_authController.roleData as WargaModel).nik; + } + return null; + } + // Getter untuk foto profil String? get profilePhotoUrl { // 1. Coba ambil dari fotoProfil yang sudah disimpan @@ -121,35 +135,18 @@ class WargaDashboardController extends GetxController { void loadUserData() { currentUser.value = _authController.baseUser; - // Tambahkan log debugging - print('DEBUG WARGA: Memuat data user dari AuthController'); - print('DEBUG WARGA: baseUser: ${_authController.baseUser}'); - print('DEBUG WARGA: roleData: ${_authController.roleData}'); - print('DEBUG WARGA: nama yang akan ditampilkan: $nama'); - print( - 'DEBUG WARGA: displayName dari auth controller: ${_authController.displayName}'); - + // Kurangi log debugging if (_authController.userData != null) { - print( - 'DEBUG WARGA: userData ada, role: ${_authController.userData!.baseUser.roleName}'); - if (_authController.isWarga) { - print('DEBUG WARGA: User adalah warga'); var wargaData = _authController.roleData; - print('DEBUG WARGA: Data warga: ${wargaData?.namaLengkap}'); // Ambil foto profil dari wargaData jika ada if (wargaData != null && wargaData.fotoProfil != null && wargaData.fotoProfil!.isNotEmpty) { fotoProfil.value = wargaData.fotoProfil!; - print('DEBUG WARGA: Foto profil dari roleData: ${fotoProfil.value}'); } - } else { - print('DEBUG WARGA: User bukan warga'); } - } else { - print('DEBUG WARGA: userData null'); } // Ambil foto profil dari database @@ -169,9 +166,6 @@ class WargaDashboardController extends GetxController { if (wargaData != null && wargaData['foto_profil'] != null) { fotoProfil.value = wargaData['foto_profil']; - print('DEBUG WARGA: Foto profil dari API: ${fotoProfil.value}'); - } else { - print('DEBUG WARGA: Foto profil tidak ditemukan atau null'); } } catch (e) { print('Error fetching profile photo: $e'); @@ -211,18 +205,13 @@ class WargaDashboardController extends GetxController { // Reset data terlebih dahulu untuk memastikan tidak ada data lama yang tersimpan penerimaPenyaluran.clear(); - // Log untuk debugging - print('DEBUG PENERIMAAN: Memulai fetchPenerimaPenyaluran()'); - // Pastikan user sudah login dan memiliki ID if (user?.id == null) { - print('DEBUG PENERIMAAN: User ID null, tidak bisa mengambil data'); return []; } // Gunakan langsung ID pengguna sebagai warga_id final wargaId = user!.id; - print('DEBUG PENERIMAAN: Mengambil data untuk warga ID: $wargaId'); // Ambil data penerima penyaluran dengan join ke warga, stok bantuan, dan penyaluran bantuan final response = @@ -240,9 +229,6 @@ class WargaDashboardController extends GetxController { ) ''').eq('warga_id', wargaId).order('created_at', ascending: false); - print( - 'DEBUG PENERIMAAN: Respons diterima dengan ${response.length} item'); - final List penerima = []; // Loop melalui setiap data penerima @@ -329,7 +315,6 @@ class WargaDashboardController extends GetxController { var model = PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData); penerima.add(model); - print('DEBUG PENERIMAAN: Berhasil parse item: ${model.id}'); } catch (parseError) { print('DEBUG PENERIMAAN: Error parsing item: $parseError'); print('DEBUG PENERIMAAN: Data yang gagal di-parse: $item'); @@ -340,19 +325,10 @@ class WargaDashboardController extends GetxController { if (penerima.isNotEmpty) { // Update nilai observable penerimaPenyaluran.assignAll(penerima); - print( - 'DEBUG PENERIMAAN: Berhasil assign ${penerima.length} item ke list'); var diterima = penerima.where((p) => p.statusPenerimaan == 'DITERIMA').length; totalPenyaluranDiterima.value = diterima; - - // Log untuk debugging - print( - 'Berhasil memuat ${penerima.length} data penerimaan untuk warga ID: $wargaId'); - } else { - print( - 'DEBUG PENERIMAAN: Tidak ada data penerimaan yang berhasil di-parse'); } return penerima; @@ -628,12 +604,23 @@ class WargaDashboardController extends GetxController { } } + // Metode untuk mengatur apakah auto-refresh diaktifkan atau tidak + void setAutoRefreshEnabled(bool enabled) { + _autoRefreshEnabled.value = enabled; + } + // Metode untuk refresh data setelah update profil atau kembali ke halaman - Future refreshData() async { - print('DEBUG WARGA: Memulai refresh data...'); + Future refreshData({bool silent = false}) async { + // Cek apakah auto-refresh diaktifkan + if (!_autoRefreshEnabled.value) { + if (!silent) print('Auto-refresh dinonaktifkan, melewati refresh data'); + return; + } + + if (!silent) print('Memulai refresh data...'); await _authController.refreshUserData(); // Refresh data dari server loadUserData(); // Muat ulang data ke variabel lokal fetchData(); // Ambil data terkait lainnya - print('DEBUG WARGA: Refresh data selesai'); + if (!silent) print('Refresh data selesai'); } } diff --git a/lib/app/modules/warga/views/form_pengaduan_view.dart b/lib/app/modules/warga/views/form_pengaduan_view.dart new file mode 100644 index 0000000..d14a9dd --- /dev/null +++ b/lib/app/modules/warga/views/form_pengaduan_view.dart @@ -0,0 +1,306 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; + +class FormPengaduanView extends StatefulWidget { + final String uidPenerimaan; + final String? judul; + final String? deskripsi; + final List? selectedImages; + + const FormPengaduanView({ + Key? key, + required this.uidPenerimaan, + this.judul, + this.deskripsi, + this.selectedImages, + }) : super(key: key); + + @override + State createState() => _FormPengaduanViewState(); +} + +class _FormPengaduanViewState extends State { + final WargaDashboardController wargaController = + Get.find(); + + static final GlobalKey _formKey = GlobalKey(); + + final TextEditingController _titleController = TextEditingController(); + final TextEditingController _descriptionController = TextEditingController(); + + bool _isSubmitting = false; + List _selectedImages = []; + + @override + void initState() { + super.initState(); + wargaController.setAutoRefreshEnabled(false); + + // Isi form dengan data yang diberikan jika ada + if (widget.judul != null) { + _titleController.text = widget.judul!; + } + if (widget.deskripsi != null) { + _descriptionController.text = widget.deskripsi!; + } + if (widget.selectedImages != null && widget.selectedImages!.isNotEmpty) { + _selectedImages = List.from(widget.selectedImages!); + } + } + + @override + void dispose() { + _titleController.dispose(); + _descriptionController.dispose(); + wargaController.setAutoRefreshEnabled(true); + super.dispose(); + } + + Future _pickImage(ImageSource source) async { + try { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage( + source: source, + imageQuality: 80, + ); + + if (image != null) { + setState(() { + _selectedImages.add(File(image.path)); + }); + } + } catch (e) { + Get.snackbar( + 'Error', + 'Tidak dapat memilih gambar: $e', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + void _removeImage(int index) { + setState(() { + _selectedImages.removeAt(index); + }); + } + + Future _submitComplaint() async { + if (_formKey.currentState!.validate() && !_isSubmitting) { + try { + setState(() { + _isSubmitting = true; + }); + + // Menutup keyboard sebelum memproses + FocusScope.of(context).unfocus(); + + // Menyiapkan data foto + List fotoPaths = + _selectedImages.map((file) => file.path).toList(); + + final success = await wargaController.addPengaduan( + judul: _titleController.text, + deskripsi: _descriptionController.text, + penerimaPenyaluranId: widget.uidPenerimaan, + fotoPengaduanPaths: fotoPaths, + ); + + if (success) { + Get.back(result: true); + } + } catch (e) { + Get.snackbar( + 'Error', + 'Gagal mengirim pengaduan: $e', + snackPosition: SnackPosition.BOTTOM, + ); + } finally { + if (mounted) { + setState(() { + _isSubmitting = false; + }); + } + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Form Pengaduan'), + backgroundColor: Get.theme.primaryColor, + foregroundColor: Colors.white, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Input Judul Pengaduan + TextFormField( + controller: _titleController, + decoration: const InputDecoration( + labelText: 'Judul Pengaduan', + border: OutlineInputBorder(), + contentPadding: + EdgeInsets.symmetric(horizontal: 12, vertical: 16), + ), + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Judul pengaduan tidak boleh kosong'; + } + return null; + }, + ), + const SizedBox(height: 16), + // Input Deskripsi Pengaduan + TextFormField( + controller: _descriptionController, + decoration: const InputDecoration( + labelText: 'Deskripsi Pengaduan', + border: OutlineInputBorder(), + alignLabelWithHint: true, + contentPadding: + EdgeInsets.symmetric(horizontal: 12, vertical: 16), + ), + maxLines: 5, + textInputAction: TextInputAction.done, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Deskripsi pengaduan tidak boleh kosong'; + } + return null; + }, + ), + const SizedBox(height: 20), + // Button untuk memilih gambar + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () => _pickImage(ImageSource.gallery), + icon: const Icon(Icons.photo_library), + label: const Text('Galeri'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton.icon( + onPressed: () => _pickImage(ImageSource.camera), + icon: const Icon(Icons.camera_alt), + label: const Text('Kamera'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + ], + ), + + // Tampilkan gambar-gambar jika sudah dipilih + if (_selectedImages.isNotEmpty) ...[ + const SizedBox(height: 16), + const Text( + 'Foto Pengaduan:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Container( + height: 120, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: _selectedImages.length, + itemBuilder: (context, index) { + return Stack( + children: [ + Container( + width: 120, + height: 120, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + image: FileImage(_selectedImages[index]), + fit: BoxFit.cover, + ), + ), + ), + Positioned( + top: 5, + right: 13, + child: GestureDetector( + onTap: () => _removeImage(index), + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.close, + color: Colors.white, + size: 16, + ), + ), + ), + ), + ], + ); + }, + ), + ), + ], + const SizedBox(height: 24), + // Button untuk submit pengaduan + SizedBox( + height: 48, + child: ElevatedButton( + onPressed: _isSubmitting ? null : _submitComplaint, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.orange.shade700, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: _isSubmitting + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Text( + 'Kirim Pengaduan', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/warga/views/warga_dashboard_view.dart b/lib/app/modules/warga/views/warga_dashboard_view.dart index b0b7cc6..9489369 100644 --- a/lib/app/modules/warga/views/warga_dashboard_view.dart +++ b/lib/app/modules/warga/views/warga_dashboard_view.dart @@ -5,10 +5,11 @@ import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_con import 'package:penyaluran_app/app/routes/app_pages.dart'; import 'package:penyaluran_app/app/widgets/bantuan_card.dart'; import 'package:penyaluran_app/app/widgets/section_header.dart'; +import 'package:penyaluran_app/app/data/models/penerima_penyaluran_model.dart'; +import 'package:penyaluran_app/app/modules/warga/views/form_pengaduan_view.dart'; class WargaDashboardView extends GetView { const WargaDashboardView({super.key}); - @override Widget build(BuildContext context) { return Scaffold( @@ -16,7 +17,6 @@ class WargaDashboardView extends GetView { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } - return RefreshIndicator( onRefresh: () async { controller.fetchData(); @@ -150,10 +150,10 @@ class WargaDashboardView extends GetView { child: Column( children: [ _buildInfoRow( - icon: Icons.home_rounded, - iconColor: Colors.blue.shade300, - label: 'Alamat', - value: controller.alamat ?? 'Alamat tidak tersedia', + icon: Icons.numbers_rounded, + iconColor: Colors.green.shade300, + label: 'NIK', + value: controller.nik ?? 'NIK tidak tersedia', ), const Padding( padding: EdgeInsets.symmetric(vertical: 8), @@ -175,31 +175,19 @@ class WargaDashboardView extends GetView { label: 'Desa', value: controller.desa ?? 'Desa tidak tersedia', ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Divider(height: 1), + ), + _buildInfoRow( + icon: Icons.home_rounded, + iconColor: Colors.blue.shade300, + label: 'Alamat Lengkap', + value: controller.alamat ?? 'Alamat tidak tersedia', + ), ], ), ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _buildActionButton( - icon: Icons.edit_rounded, - label: 'Edit Profil', - color: Colors.blue.shade700, - onTap: () => Get.toNamed(Routes.PROFILE), - ), - ), - const SizedBox(width: 10), - Expanded( - child: _buildActionButton( - icon: Icons.notifications_rounded, - label: 'Notifikasi', - color: Colors.amber.shade700, - onTap: () => Get.toNamed(Routes.NOTIFIKASI), - ), - ), - ], - ), ], ), ), @@ -299,7 +287,6 @@ class WargaDashboardView extends GetView { } Widget _buildStatisticSection() { - // Data untuk statistik final totalBantuan = controller.penerimaPenyaluran.length; final totalDiterima = controller.penerimaPenyaluran .where((item) => item.statusPenerimaan == 'DITERIMA') @@ -341,7 +328,6 @@ class WargaDashboardView extends GetView { ), ], ), - // Progress bar untuk persentase bantuan yang diterima if (totalBantuan > 0) ...[ const SizedBox(height: 16), Text( @@ -443,7 +429,6 @@ class WargaDashboardView extends GetView { decimalDigits: 0, ); - // Hitung total bantuan uang dan non-uang double totalUang = 0; Map totalNonUang = {}; @@ -742,4 +727,146 @@ class WargaDashboardView extends GetView { ), ); } + + void _showBuatPengaduanDialog(BuildContext context) { + // Daftar penerimaan bantuan yang dapat diadukan (status DITERIMA) + final bantuanDiterima = controller.penerimaPenyaluran + .where((item) => item.statusPenerimaan == 'DITERIMA') + .toList(); + + // Jika tidak ada bantuan yang diterima + if (bantuanDiterima.isEmpty) { + Get.snackbar( + 'Informasi', + 'Tidak ada bantuan yang sudah diterima untuk dapat diajukan pengaduan', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange, + colorText: Colors.white, + ); + return; + } + + // Variabel untuk menyimpan pilihan penerimaan + PenerimaPenyaluranModel? selectedPenerimaan = bantuanDiterima.first; + + Get.dialog( + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.report_problem, + color: Colors.orange.shade700, + ), + const SizedBox(width: 10), + const Text( + 'Buat Pengaduan Baru', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Get.back(), + ), + ], + ), + const SizedBox(height: 20), + const Text( + 'Pilih Bantuan yang Ingin Diadukan:', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + StatefulBuilder( + builder: (context, setState) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade300), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: selectedPenerimaan, + items: bantuanDiterima.map((item) { + String displayText = item.namaPenyaluran ?? 'Bantuan'; + if (item.tanggalPenerimaan != null) { + displayText += + ' (${DateFormat('dd/MM/yyyy').format(item.tanggalPenerimaan!)})'; + } + + return DropdownMenuItem( + value: item, + child: Text(displayText), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setState(() { + selectedPenerimaan = value; + }); + } + }, + ), + ), + ); + }, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + if (selectedPenerimaan != null) { + Get.back(); + Get.to( + () => FormPengaduanView( + uidPenerimaan: selectedPenerimaan!.id.toString(), + ), + transition: Transition.rightToLeft, + duration: const Duration(milliseconds: 300), + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange.shade700, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text( + 'Lanjutkan', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } } diff --git a/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart b/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart index 035b7e7..def6d62 100644 --- a/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart +++ b/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart @@ -1301,13 +1301,13 @@ class WargaDetailPenerimaanView extends GetView { Get.back(); // Tutup dialog terlebih dahulu - // Tampilkan loading - Get.dialog( - const Center( - child: CircularProgressIndicator(), - ), - barrierDismissible: false, - ); + // // Tampilkan loading + // Get.dialog( + // const Center( + // child: CircularProgressIndicator(), + // ), + // barrierDismissible: false, + // ); bool success = false; try { @@ -1336,18 +1336,6 @@ class WargaDetailPenerimaanView extends GetView { // Refresh data halaman await controller.fetchPengaduan(); await controller.fetchPenerimaPenyaluran(); - - Get.snackbar( - 'Sukses', - 'Pengaduan berhasil dikirim dan data diperbarui', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - duration: const Duration(seconds: 3), - ); - - // Navigate ke halaman pengaduan jika berhasil - controller.changeTab(2); // Tab pengaduan } }, style: ElevatedButton.styleFrom( diff --git a/lib/app/modules/warga/views/warga_pengaduan_view.dart b/lib/app/modules/warga/views/warga_pengaduan_view.dart index 8ea114c..8038fc0 100644 --- a/lib/app/modules/warga/views/warga_pengaduan_view.dart +++ b/lib/app/modules/warga/views/warga_pengaduan_view.dart @@ -1,40 +1,48 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:penyaluran_app/app/data/models/penerima_penyaluran_model.dart'; import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; +import 'package:penyaluran_app/app/modules/warga/views/form_pengaduan_view.dart'; import 'package:penyaluran_app/app/utils/date_time_helper.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; class WargaPengaduanView extends GetView { const WargaPengaduanView({super.key}); @override Widget build(BuildContext context) { - return Obx(() { - if (controller.isLoading.value) { - return const Center(child: CircularProgressIndicator()); - } + return Scaffold( + body: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } - // Debug print untuk melihat jumlah item - print('DEBUG: Jumlah pengaduan tersedia: ${controller.pengaduan.length}'); + // Debug print untuk melihat jumlah item + print( + 'DEBUG: Jumlah pengaduan tersedia: ${controller.pengaduan.length}'); - return RefreshIndicator( - onRefresh: () async { - // Tambahkan delay untuk memastikan refresh indicator terlihat - await Future.delayed(const Duration(milliseconds: 300)); - controller.fetchData(); - }, - child: controller.pengaduan.isEmpty - ? ListView( - physics: const AlwaysScrollableScrollPhysics(), - children: [ - SizedBox( - height: Get.height * 0.7, - child: _buildEmptyState(), - ), - ], - ) - : _buildPengaduanList(context), - ); - }); + return RefreshIndicator( + onRefresh: () async { + // Tambahkan delay untuk memastikan refresh indicator terlihat + await Future.delayed(const Duration(milliseconds: 300)); + controller.fetchData(); + }, + child: controller.pengaduan.isEmpty + ? ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox( + height: Get.height * 0.7, + child: _buildEmptyState(), + ), + ], + ) + : _buildPengaduanList(context), + ); + }), + ); } Widget _buildEmptyState() { @@ -89,8 +97,6 @@ class WargaPengaduanView extends GetView { } final item = controller.pengaduan[index]; - print( - 'DEBUG: Membangun item pengaduan $index dengan id: ${item.id}'); // Tentukan status dan warna berdasarkan status pengaduan Color statusColor; diff --git a/lib/app/modules/warga/views/warga_view.dart b/lib/app/modules/warga/views/warga_view.dart index 019623c..4b1a0e6 100644 --- a/lib/app/modules/warga/views/warga_view.dart +++ b/lib/app/modules/warga/views/warga_view.dart @@ -16,16 +16,16 @@ class WargaView extends GetView { Widget build(BuildContext context) { // Tambahkan listener untuk refresh data saat fokus didapatkan kembali // misalnya ketika kembali dari halaman profil - WidgetsBinding.instance.addPostFrameCallback((_) { - final focusNode = FocusNode(); - FocusScope.of(context).requestFocus(focusNode); - focusNode.addListener(() { - if (focusNode.hasFocus) { - print('DEBUG WARGA: Halaman mendapatkan fokus, memuat ulang data'); - controller.refreshData(); - } - }); - }); + // WidgetsBinding.instance.addPostFrameCallback((_) { + // final focusNode = FocusNode(); + // FocusScope.of(context).requestFocus(focusNode); + // focusNode.addListener(() { + // if (focusNode.hasFocus) { + // print('DEBUG WARGA: Halaman mendapatkan fokus, memuat ulang data'); + // controller.refreshData(); + // } + // }); + // }); return Scaffold( key: scaffoldKey, diff --git a/lib/app/services/supabase_service.dart b/lib/app/services/supabase_service.dart index dcfb3e2..655819c 100644 --- a/lib/app/services/supabase_service.dart +++ b/lib/app/services/supabase_service.dart @@ -40,14 +40,11 @@ class SupabaseService extends GetxService { print('DEBUG: Auth state changed: $event'); if (event == AuthChangeEvent.signedIn) { - print('DEBUG: User signed in'); _isSessionInitialized = true; } else if (event == AuthChangeEvent.signedOut) { - print('DEBUG: User signed out'); _cachedUserProfile = null; _isSessionInitialized = false; } else if (event == AuthChangeEvent.tokenRefreshed) { - print('DEBUG: Token refreshed'); _isSessionInitialized = true; } }); @@ -55,7 +52,6 @@ class SupabaseService extends GetxService { // Periksa apakah ada sesi yang aktif final session = client.auth.currentSession; if (session != null) { - print('DEBUG: Session aktif ditemukan saat inisialisasi'); _isSessionInitialized = true; } else { print('DEBUG: Tidak ada session aktif saat inisialisasi'); @@ -82,7 +78,6 @@ class SupabaseService extends GetxService { _cachedUserProfile = null; // Hapus cache saat logout _isSessionInitialized = false; await client.auth.signOut(); - print('DEBUG: Logout berhasil, sesi dihapus'); } // Metode untuk mendapatkan user saat ini @@ -99,10 +94,8 @@ class SupabaseService extends GetxService { final isValid = session.expiresAt != null && session.expiresAt! > now; if (isValid) { - print('DEBUG: Sesi valid, user terautentikasi'); return true; } else { - print('DEBUG: Sesi kedaluwarsa, user tidak terautentikasi'); return false; } } @@ -480,6 +473,51 @@ class SupabaseService extends GetxService { } } + // Metode untuk mendapatkan jumlah penyaluran berdasarkan status + Future?> getStatusPenyaluran() async { + try { + final result = { + 'dijadwalkan': 0, + 'aktif': 0, + 'batal': 0, + 'terlaksana': 0 + }; + + // Mendapatkan jumlah penyaluran dengan status DIJADWALKAN + final dijadwalkanResponse = await client + .from('penyaluran_bantuan') + .select('id') + .eq('status', 'DIJADWALKAN'); + result['dijadwalkan'] = dijadwalkanResponse.length; + + // Mendapatkan jumlah penyaluran dengan status AKTIF + final aktifResponse = await client + .from('penyaluran_bantuan') + .select('id') + .eq('status', 'AKTIF'); + result['aktif'] = aktifResponse.length; + + // Mendapatkan jumlah penyaluran dengan status BATAL + final batalResponse = await client + .from('penyaluran_bantuan') + .select('id') + .eq('status', 'BATALTERLAKSANA'); + result['batal'] = batalResponse.length; + + // Mendapatkan jumlah penyaluran dengan status TERLAKSANA + final terlaksanaResponse = await client + .from('penyaluran_bantuan') + .select('id') + .eq('status', 'TERLAKSANA'); + result['terlaksana'] = terlaksanaResponse.length; + + return result; + } catch (e) { + print('Error getting status penyaluran: $e'); + return null; + } + } + Future>?> getNotifikasiBelumDibaca( String userId) async { try { @@ -499,16 +537,8 @@ class SupabaseService extends GetxService { } // Jadwal penyaluran methods - Future>?> getJadwalHariIni() async { + Future>?> getJadwalAktif() async { try { - final now = DateTime.now(); - final today = DateTime(now.year, now.month, now.day); - final tomorrow = today.add(const Duration(days: 1)); - - // Konversi ke UTC untuk query ke database - final todayUtc = today.toUtc().toIso8601String(); - final tomorrowUtc = tomorrow.toUtc().toIso8601String(); - final response = await client .from('penyaluran_bantuan') .select(''' @@ -518,15 +548,12 @@ class SupabaseService extends GetxService { id, nama, alamat_lengkap ) ''') - .gte('tanggal_penyaluran', todayUtc) - .lt('tanggal_penyaluran', tomorrowUtc) - .inFilter('status', ['AKTIF', 'DIJADWALKAN']); - - print("hari ini $response"); + .eq('status', 'AKTIF') + .order('tanggal_penyaluran', ascending: true); return response; } catch (e) { - print('Error getting jadwal hari ini: $e'); + print('Error getting jadwal aktif: $e'); return null; } } @@ -1125,8 +1152,7 @@ class SupabaseService extends GetxService { .from('tindakan_pengaduan') .select(''' *, - petugas:petugas_id(id, nama_lengkap, nip), - verifikator:verifikator_id(id, nama_lengkap, nip) + petugas:petugas_id(id, nama_lengkap, nip) ''') .eq('pengaduan_id', pengaduanId) .order('created_at', ascending: false); @@ -1437,7 +1463,6 @@ class SupabaseService extends GetxService { try { // Buat map untuk update data final Map updateData = { - 'nama': nama, 'nama_lengkap': nama, // Untuk konsistensi dengan field nama_lengkap 'no_hp': noHp, 'updated_at': DateTime.now().toIso8601String(), @@ -1610,68 +1635,68 @@ class SupabaseService extends GetxService { return []; } - // Metode untuk memperbarui status penerimaan bantuan - Future updateStatusPenerimaan(int penerimaId, String status, - {DateTime? tanggalPenerimaan, - String? buktiPenerimaan, - String? keterangan}) async { - try { - // Periksa petugas ID - final petugasId = client.auth.currentUser?.id; - if (petugasId == null) { - throw Exception('ID petugas tidak ditemukan'); - } + // // Metode untuk memperbarui status penerimaan bantuan + // Future updateStatusPenerimaan(int penerimaId, String status, + // {DateTime? tanggalPenerimaan, + // String? buktiPenerimaan, + // String? keterangan}) async { + // try { + // // Periksa petugas ID + // final petugasId = client.auth.currentUser?.id; + // if (petugasId == null) { + // throw Exception('ID petugas tidak ditemukan'); + // } - final Map updateData = { - 'status_penerimaan': status, - }; + // final Map updateData = { + // 'status_penerimaan': status, + // }; - if (tanggalPenerimaan != null) { - updateData['tanggal_penerimaan'] = tanggalPenerimaan.toIso8601String(); - } + // if (tanggalPenerimaan != null) { + // updateData['tanggal_penerimaan'] = tanggalPenerimaan.toIso8601String(); + // } - if (buktiPenerimaan != null) { - updateData['bukti_penerimaan'] = buktiPenerimaan; - } + // if (buktiPenerimaan != null) { + // updateData['bukti_penerimaan'] = buktiPenerimaan; + // } - if (keterangan != null) { - updateData['keterangan'] = keterangan; - } + // if (keterangan != null) { + // updateData['keterangan'] = keterangan; + // } - // Update status penerimaan - await client - .from('penerima_penyaluran') - .update(updateData) - .eq('id', penerimaId); + // // Update status penerimaan + // await client + // .from('penerima_penyaluran') + // .update(updateData) + // .eq('id', penerimaId); - // Jika status adalah DITERIMA, kurangi stok - if (status.toUpperCase() == 'DITERIMA') { - // Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah) - final penerimaData = await client - .from('penerima_penyaluran') - .select('penyaluran_bantuan_id, stok_bantuan_id, jumlah') - .eq('id', penerimaId) - .single(); + // // Jika status adalah DITERIMA, kurangi stok + // if (status.toUpperCase() == 'DITERIMA') { + // // Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah) + // final penerimaData = await client + // .from('penerima_penyaluran') + // .select('penyaluran_bantuan_id, stok_bantuan_id, jumlah') + // .eq('id', penerimaId) + // .single(); - if (penerimaData != null) { - final String penyaluranId = penerimaData['penyaluran_bantuan_id']; - final String stokBantuanId = penerimaData['stok_bantuan_id']; - final double jumlah = penerimaData['jumlah'] is int - ? penerimaData['jumlah'].toDouble() - : penerimaData['jumlah']; + // if (penerimaData != null) { + // final String penyaluranId = penerimaData['penyaluran_bantuan_id']; + // final String stokBantuanId = penerimaData['stok_bantuan_id']; + // final double jumlah = penerimaData['jumlah'] is int + // ? penerimaData['jumlah'].toDouble() + // : penerimaData['jumlah']; - // Kurangi stok dan catat riwayat - await kurangiStokDariPenyaluran( - penyaluranId, stokBantuanId, jumlah, petugasId); - } - } + // // Kurangi stok dan catat riwayat + // await kurangiStokDariPenyaluran( + // penyaluranId, stokBantuanId, jumlah, petugasId); + // } + // } - return true; - } catch (e) { - print('Error updating status penerimaan: $e'); - return false; - } - } + // return true; + // } catch (e) { + // print('Error updating status penerimaan: $e'); + // return false; + // } + // } // Metode untuk mendapatkan semua kategori bantuan Future>?> getAllKategoriBantuan() async { @@ -2024,7 +2049,7 @@ class SupabaseService extends GetxService { 'stok_bantuan_id': stokBantuanId, 'jenis_perubahan': 'pengurangan', 'jumlah': jumlah, - 'sumber': 'penyaluran', + 'sumber': 'penerimaan', 'id_referensi': penyaluranId, 'created_by_id': petugasId, 'created_at': DateTime.now().toIso8601String() @@ -2176,4 +2201,36 @@ class SupabaseService extends GetxService { throw e; // Re-throw untuk penanganan di tingkat yang lebih tinggi } } + + // Tambahkan metode untuk mendapatkan data penitipan berdasarkan ID + Future?> getPenitipanById(String id) async { + try { + final response = await client.from('penitipan_bantuan').select(''' + *, + donatur:donatur_id(*), + petugas_desa:petugas_desa_id(*) + ''').eq('id', id).single(); + return response; + } catch (e) { + print('Error getting penitipan by id: $e'); + return null; + } + } + + // Tambahkan metode untuk mendapatkan data penerimaan berdasarkan ID + Future?> getPenerimaanById(String id) async { + try { + final response = await client.from('penerima_penyaluran').select(''' + *, + warga:warga_id(*), + penyaluran_bantuan:penyaluran_bantuan_id(*, + petugas_desa:petugas_id(*) + ) + ''').eq('id', id).single(); + return response; + } catch (e) { + print('Error getting penerimaan by id: $e'); + return null; + } + } }