From aa73508108248fd0c9bc50066f4f2fa61d3fd355 Mon Sep 17 00:00:00 2001 From: Khafidh Fuadi Date: Mon, 17 Mar 2025 19:21:38 +0700 Subject: [PATCH] Tambahkan fungsionalitas untuk mengelola feedback dan rating pengaduan. Perbarui model PengaduanModel dan TindakanPengaduanModel untuk menyertakan properti baru feedbackWarga dan ratingWarga. Modifikasi tampilan di WargaDetailPengaduanView dan DetailPengaduanView untuk menampilkan dan mengumpulkan feedback dari warga. Implementasikan metode baru di WargaDashboardController dan SupabaseService untuk menambahkan dan memperbarui feedback pengaduan. --- .../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 +- lib/app/data/models/pengaduan_model.dart | 8 + .../data/models/tindakan_pengaduan_model.dart | 8 - .../views/detail_pengaduan_view.dart | 217 +++-- .../warga_dashboard_controller.dart | 91 +- .../warga/views/detail_pengaduan_view.dart | 876 +++++++++++++++--- lib/app/services/supabase_service.dart | 30 + 10 files changed, 1025 insertions(+), 301 deletions(-) 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 4cc33e6..e4b0d5a 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 524a545..bf80186 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 6bb20b3..1cd8fd0 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 7f7a984..783eec6 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/data/models/pengaduan_model.dart b/lib/app/data/models/pengaduan_model.dart index 5a50127..c042905 100644 --- a/lib/app/data/models/pengaduan_model.dart +++ b/lib/app/data/models/pengaduan_model.dart @@ -13,6 +13,8 @@ class PengaduanModel { final DateTime? updatedAt; final Map? penerimaPenyaluran; final Map? warga; + final String? feedbackWarga; + final int? ratingWarga; PengaduanModel({ this.id, @@ -27,6 +29,8 @@ class PengaduanModel { this.updatedAt, this.penerimaPenyaluran, this.warga, + this.feedbackWarga, + this.ratingWarga, }); factory PengaduanModel.fromRawJson(String str) => @@ -53,6 +57,8 @@ class PengaduanModel { : null, penerimaPenyaluran: json["penerima_penyaluran"], warga: json["warga"], + feedbackWarga: json["feedback_warga"], + ratingWarga: json["rating_warga"], ); Map toJson() => { @@ -68,6 +74,8 @@ class PengaduanModel { "updated_at": updatedAt?.toIso8601String(), "penerima_penyaluran": penerimaPenyaluran, "warga": warga, + "feedback_warga": feedbackWarga, + "rating_warga": ratingWarga, }; // Getter untuk mendapatkan informasi penyaluran bantuan diff --git a/lib/app/data/models/tindakan_pengaduan_model.dart b/lib/app/data/models/tindakan_pengaduan_model.dart index b23d319..6121112 100644 --- a/lib/app/data/models/tindakan_pengaduan_model.dart +++ b/lib/app/data/models/tindakan_pengaduan_model.dart @@ -18,8 +18,6 @@ class TindakanPengaduanModel { final DateTime? createdAt; final DateTime? updatedAt; final double? biayaTindakan; - final String? feedbackWarga; - final int? ratingWarga; final Map? petugas; // Data petugas yang melakukan tindakan final Map? verifikator; // Data petugas yang memverifikasi @@ -41,8 +39,6 @@ class TindakanPengaduanModel { this.createdAt, this.updatedAt, this.biayaTindakan, - this.feedbackWarga, - this.ratingWarga, this.petugas, this.verifikator, }); @@ -83,8 +79,6 @@ class TindakanPengaduanModel { biayaTindakan: json["biaya_tindakan"] != null ? double.parse(json["biaya_tindakan"].toString()) : null, - feedbackWarga: json["feedback_warga"], - ratingWarga: json["rating_warga"], petugas: json["petugas"], verifikator: json["verifikator"], ); @@ -107,8 +101,6 @@ class TindakanPengaduanModel { "created_at": createdAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(), "biaya_tindakan": biayaTindakan, - "feedback_warga": feedbackWarga, - "rating_warga": ratingWarga, "petugas": petugas, "verifikator": verifikator, }; diff --git a/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart b/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart index bd43001..efc55a6 100644 --- a/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart +++ b/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart @@ -188,6 +188,14 @@ class DetailPengaduanView extends GetView { const SizedBox(height: 24), + // Feedback warga jika status SELESAI + if (pengaduan.status?.toUpperCase() == 'SELESAI' && + (pengaduan.feedbackWarga != null || + pengaduan.ratingWarga != null)) + _buildFeedbackSection(context, pengaduan), + + const SizedBox(height: 24), + // Timeline tindakan _buildTindakanTimeline(context, tindakanList), ], @@ -450,6 +458,75 @@ class DetailPengaduanView extends GetView { const SizedBox(height: 16), // Panel status pengaduan _buildStatusPanel(context, pengaduan), + + // Tampilkan feedback dan rating warga jika ada + if (pengaduan.status?.toUpperCase() == 'SELESAI' && + (pengaduan.feedbackWarga != null || + pengaduan.ratingWarga != null)) + Column( + children: [ + const SizedBox(height: 16), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.amber.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.amber.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Feedback Warga', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Colors.amber, + ), + ), + if (pengaduan.ratingWarga != null) + Row( + children: List.generate(5, (index) { + return Icon( + index < (pengaduan.ratingWarga ?? 0) + ? Icons.star + : Icons.star_border, + color: Colors.amber, + size: 16, + ); + }), + ), + ], + ), + const SizedBox(height: 8), + if (pengaduan.feedbackWarga != null && + pengaduan.feedbackWarga!.isNotEmpty) + Text( + pengaduan.feedbackWarga!, + style: TextStyle( + fontSize: 13, + color: Colors.amber.shade900, + fontStyle: FontStyle.italic, + ), + ) + else + Text( + 'Warga belum memberikan komentar', + style: TextStyle( + fontSize: 13, + color: Colors.grey.shade600, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ), + ], + ), ], ), ), @@ -1121,66 +1198,6 @@ class DetailPengaduanView extends GetView { ), ], - // Feedback warga (jika ada) - if (tindakan.feedbackWarga != null && - tindakan.feedbackWarga!.isNotEmpty) ...[ - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.amber.shade50, - borderRadius: BorderRadius.circular(4), - border: Border.all(color: Colors.amber.shade200), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.comment, - size: 16, - color: Colors.amber.shade800, - ), - const SizedBox(width: 4), - Text( - 'Feedback Warga:', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, - color: Colors.amber.shade800, - ), - ), - const Spacer(), - if (tindakan.ratingWarga != null) ...[ - Row( - children: List.generate(5, (index) { - return Icon( - index < (tindakan.ratingWarga ?? 0) - ? Icons.star - : Icons.star_border, - color: Colors.amber, - size: 16, - ); - }), - ), - ], - ], - ), - const SizedBox(height: 4), - Text( - tindakan.feedbackWarga!, - style: TextStyle( - fontSize: 13, - color: Colors.amber.shade900, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ), - ], - const SizedBox(height: 8), // Footer dengan info petugas dan tanggal @@ -2192,4 +2209,84 @@ class DetailPengaduanView extends GetView { ), ); } + + // Widget untuk menampilkan feedback dan rating warga + Widget _buildFeedbackSection(BuildContext context, PengaduanModel pengaduan) { + return Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Feedback Warga', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppTheme.primaryColor, + ), + ), + const Divider(height: 24), + if (pengaduan.ratingWarga != null) + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + const Text( + 'Rating: ', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + Row( + children: List.generate(5, (index) { + return Icon( + index < (pengaduan.ratingWarga ?? 0) + ? Icons.star + : Icons.star_border, + color: Colors.amber, + size: 20, + ); + }), + ), + ], + ), + ), + if (pengaduan.feedbackWarga != null && + pengaduan.feedbackWarga!.isNotEmpty) + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.amber.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.amber.shade200), + ), + child: Text( + pengaduan.feedbackWarga!, + style: TextStyle( + fontSize: 14, + color: Colors.amber.shade900, + fontStyle: FontStyle.italic, + ), + ), + ) + else + Text( + 'Warga belum memberikan komentar', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ), + ); + } } diff --git a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart index c850e90..5277ff5 100644 --- a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart +++ b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart @@ -5,6 +5,7 @@ import 'package:penyaluran_app/app/data/models/pengajuan_kelayakan_bantuan_model import 'package:penyaluran_app/app/data/models/user_model.dart'; import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; import 'package:penyaluran_app/app/services/supabase_service.dart'; +import 'package:flutter/material.dart'; class WargaDashboardController extends GetxController { final AuthController _authController = Get.find(); @@ -86,9 +87,6 @@ class WargaDashboardController extends GetxController { // Fungsi untuk mengambil data penerima penyaluran Future fetchPenerimaPenyaluran() async { try { - print('Memulai fetchPenerimaPenyaluran()'); - print('User ID: ${user?.id}'); - // Pertama, cari warga_id berdasarkan user_id final wargaResponse = await _supabaseService.client .from('warga') @@ -96,15 +94,7 @@ class WargaDashboardController extends GetxController { .eq('user_id', user!.id) .single(); - print('Warga response: $wargaResponse'); - - if (wargaResponse == null) { - print('Tidak ditemukan data warga untuk user_id: ${user!.id}'); - return; - } - final wargaId = wargaResponse['id']; - print('Warga ID: $wargaId'); // Ambil data penerima penyaluran dengan join ke warga, stok bantuan, dan penyaluran bantuan final response = @@ -121,30 +111,20 @@ class WargaDashboardController extends GetxController { ) ''').eq('warga_id', wargaId).order('created_at', ascending: false); - print('Response dari API: $response'); - if (response != null) { final List penerima = []; for (var item in response) { - print('Memproses item: $item'); - Map sanitizedPenerimaData = Map.from(item); - print('Data yang disanitasi: $sanitizedPenerimaData'); - if (sanitizedPenerimaData['jumlah_bantuan'] is String) { var jumlahBantuan = double.tryParse( sanitizedPenerimaData['jumlah_bantuan'] as String); - print( - 'Konversi jumlah_bantuan dari String ke double: $jumlahBantuan'); sanitizedPenerimaData['jumlah_bantuan'] = jumlahBantuan; } // Tambahkan informasi apakah bantuan uang atau bukan dan satuan if (sanitizedPenerimaData['stok_bantuan'] != null) { - print('Stok bantuan: ${sanitizedPenerimaData['stok_bantuan']}'); - // Cek apakah bantuan uang final isUang = sanitizedPenerimaData['stok_bantuan']['is_uang'] ?? false; @@ -163,15 +143,10 @@ class WargaDashboardController extends GetxController { ''; sanitizedPenerimaData['kategori_nama'] = kategoriNama; } - - print('Is Uang: $isUang, Satuan: $satuan'); } // Tambahkan informasi dari penyaluran bantuan if (sanitizedPenerimaData['penyaluran_bantuan'] != null) { - print( - 'Penyaluran bantuan: ${sanitizedPenerimaData['penyaluran_bantuan']}'); - // Ambil nama penyaluran final namaPenyaluran = sanitizedPenerimaData['penyaluran_bantuan']['nama'] ?? ''; @@ -196,26 +171,20 @@ class WargaDashboardController extends GetxController { ''; sanitizedPenerimaData['lokasi_penyaluran_alamat'] = lokasiAlamat; } - - print('Nama Penyaluran: $namaPenyaluran'); } var model = PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData); - print('Model yang dibuat: $model'); penerima.add(model); } - print('Total data yang diproses: ${penerima.length}'); penerimaPenyaluran.assignAll(penerima); var diterima = penerima.where((p) => p.statusPenerimaan == 'DITERIMA').length; - print('Total penyaluran diterima: $diterima'); totalPenyaluranDiterima.value = diterima; } - } catch (e, stackTrace) { + } catch (e) { print('Error fetchPenerimaPenyaluran: $e'); - print('Stack trace: $stackTrace'); } } @@ -230,12 +199,10 @@ class WargaDashboardController extends GetxController { .single(); if (wargaResponse == null) { - print('Tidak ditemukan data warga untuk user_id: ${user!.id}'); return; } final wargaId = wargaResponse['id']; - print('Warga ID untuk pengajuan kelayakan: $wargaId'); final response = await _supabaseService.client .from('xx02_pengajuan_kelayakan_bantuan') @@ -275,7 +242,6 @@ class WargaDashboardController extends GetxController { try { final wargaData = await _supabaseService.getWargaByUserId(); if (wargaData == null) { - print('Data warga tidak ditemukan'); return; } @@ -315,7 +281,6 @@ class WargaDashboardController extends GetxController { jumlahNotifikasiBelumDibaca.value = response.count; } catch (e) { - print('Error fetching notifikasi: $e'); jumlahNotifikasiBelumDibaca.value = 0; } } @@ -326,7 +291,6 @@ class WargaDashboardController extends GetxController { } void goToPengajuanDetail() { - // TODO: Implementasi navigasi ke halaman detail pengajuan // Untuk saat ini, belum ada halaman detail pengajuan } @@ -374,11 +338,60 @@ class WargaDashboardController extends GetxController { return result; } catch (e) { - print('Error getting detail pengaduan: $e'); return { 'pengaduan': null, 'tindakan': [], }; } } + + // Metode untuk menambahkan feedback dan rating pengaduan + Future addPengaduanFeedback( + String pengaduanId, String feedback, int rating) async { + try { + await _supabaseService.addPengaduanFeedback( + pengaduanId, feedback, rating); + fetchData(); // Refresh data + Get.snackbar( + 'Berhasil', + 'Feedback berhasil dikirim', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Gagal mengirim feedback: ${e.toString()}', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } + + // Metode untuk memperbarui feedback dan rating pengaduan + Future updatePengaduanFeedback( + String pengaduanId, String feedback, int rating) async { + try { + await _supabaseService.updatePengaduanFeedback( + pengaduanId, feedback, rating); + fetchData(); // Refresh data + Get.snackbar( + 'Berhasil', + 'Feedback berhasil diperbarui', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Gagal memperbarui feedback: ${e.toString()}', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } } diff --git a/lib/app/modules/warga/views/detail_pengaduan_view.dart b/lib/app/modules/warga/views/detail_pengaduan_view.dart index 378a63b..d90088d 100644 --- a/lib/app/modules/warga/views/detail_pengaduan_view.dart +++ b/lib/app/modules/warga/views/detail_pengaduan_view.dart @@ -7,6 +7,9 @@ import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_con import 'package:penyaluran_app/app/theme/app_theme.dart'; import 'package:timeline_tile/timeline_tile.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:penyaluran_app/app/widgets/indicators/status_pill.dart'; +import 'package:penyaluran_app/app/widgets/section_header.dart'; +import 'package:penyaluran_app/app/widgets/cards/info_card.dart'; import 'dart:io'; class WargaDetailPengaduanView extends GetView { @@ -62,6 +65,36 @@ class WargaDetailPengaduanView extends GetView { return _buildDetailContent(context, pengaduan, tindakanList); }, ), + floatingActionButton: FutureBuilder>( + future: controller.getDetailPengaduan(pengaduanId), + builder: (context, snapshot) { + if (!snapshot.hasData) return const SizedBox(); + + final data = snapshot.data; + if (data == null || data['pengaduan'] == null) + return const SizedBox(); + + final pengaduan = PengaduanModel.fromJson(data['pengaduan']); + + // Tampilkan tombol feedback hanya jika status pengaduan SELESAI + // dan belum ada feedback atau rating + if (pengaduan.status?.toUpperCase() == 'SELESAI' && + (pengaduan.feedbackWarga == null || + pengaduan.ratingWarga == null)) { + return FloatingActionButton.extended( + onPressed: () { + _showFeedbackDialog(context, pengaduan); + }, + backgroundColor: AppTheme.primaryColor, + icon: const Icon(Icons.star, color: Colors.white), + label: const Text('Beri Rating', + style: TextStyle(color: Colors.white)), + ); + } + + return const SizedBox(); + }, + ), ); } @@ -108,6 +141,14 @@ class WargaDetailPengaduanView extends GetView { const SizedBox(height: 24), + // Tampilkan feedback dan rating jika sudah ada + if (pengaduan.status?.toUpperCase() == 'SELESAI' && + (pengaduan.feedbackWarga != null || + pengaduan.ratingWarga != null)) + _buildFeedbackSection(context, pengaduan), + + const SizedBox(height: 24), + // Timeline tindakan _buildTindakanTimeline(context, tindakanList), ], @@ -115,6 +156,206 @@ class WargaDetailPengaduanView extends GetView { ); } + // Widget untuk menampilkan feedback dan rating + Widget _buildFeedbackSection(BuildContext context, PengaduanModel pengaduan) { + return Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Feedback Anda', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppTheme.primaryColor, + ), + ), + if (pengaduan.ratingWarga != null) + Row( + children: List.generate(5, (index) { + return Icon( + index < (pengaduan.ratingWarga ?? 0) + ? Icons.star + : Icons.star_border, + color: Colors.amber, + size: 20, + ); + }), + ), + ], + ), + const Divider(height: 24), + if (pengaduan.feedbackWarga != null && + pengaduan.feedbackWarga!.isNotEmpty) + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.amber.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.amber.shade200), + ), + child: Text( + pengaduan.feedbackWarga!, + style: TextStyle( + fontSize: 14, + color: Colors.amber.shade900, + fontStyle: FontStyle.italic, + ), + ), + ) + else + Text( + 'Anda belum memberikan komentar', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ), + ); + } + + // Dialog untuk menambahkan atau mengedit feedback + void _showFeedbackDialog(BuildContext context, PengaduanModel pengaduan, + {bool isEdit = false}) { + final formKey = GlobalKey(); + final feedbackController = + TextEditingController(text: pengaduan.feedbackWarga); + int selectedRating = pengaduan.ratingWarga ?? 0; + + showDialog( + context: context, + builder: (context) => StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: Text( + 'Beri Feedback Pelayanan', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + content: Form( + key: formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Rating', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(5, (index) { + return IconButton( + onPressed: () { + setState(() { + selectedRating = index + 1; + }); + }, + icon: Icon( + index < selectedRating + ? Icons.star + : Icons.star_border, + color: Colors.amber, + size: 30, + ), + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ); + }), + ), + ), + const SizedBox(height: 16), + const Text( + 'Komentar', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + TextFormField( + controller: feedbackController, + maxLines: 3, + decoration: InputDecoration( + hintText: 'Tulis komentar Anda di sini...', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (selectedRating > 0 && + (value == null || value.isEmpty)) { + return 'Mohon berikan komentar'; + } + return null; + }, + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () async { + if (selectedRating == 0) { + Get.snackbar( + 'Peringatan', + 'Mohon berikan rating terlebih dahulu', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.orange, + colorText: Colors.white, + ); + return; + } + + if (formKey.currentState!.validate()) { + Navigator.pop(context); + + await controller.addPengaduanFeedback( + pengaduan.id!, + feedbackController.text, + selectedRating, + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + ), + child: Text('Kirim'), + ), + ], + ); + }, + ), + ); + } + Widget _buildHeaderWithStatus( BuildContext context, PengaduanModel pengaduan, @@ -143,27 +384,7 @@ class WargaDetailPengaduanView extends GetView { ), ), ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: statusColor, - ), - ), - child: Text( - statusText, - style: TextStyle( - color: statusColor, - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ), + _getStatusPill(pengaduan.status), ], ), const SizedBox(height: 12), @@ -194,12 +415,176 @@ class WargaDetailPengaduanView extends GetView { ), ], ), + const SizedBox(height: 16), + // Panel status pengaduan + _buildStatusPanel(context, pengaduan), ], ), ), ); } + // Helper method untuk mendapatkan StatusPill berdasarkan status + StatusPill _getStatusPill(String? status) { + switch (status?.toUpperCase()) { + case 'MENUNGGU': + return StatusPill( + status: 'Menunggu', + backgroundColor: Colors.orange, + textColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + ); + case 'TINDAKAN': + return StatusPill( + status: 'Tindakan', + backgroundColor: Colors.blue, + textColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + ); + case 'SELESAI': + return StatusPill.completed(status: 'Selesai'); + default: + return StatusPill( + status: status ?? 'Tidak Diketahui', + backgroundColor: Colors.grey, + textColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + ); + } + } + + Widget _buildStatusPanel(BuildContext context, PengaduanModel pengaduan) { + final status = pengaduan.status?.toUpperCase() ?? ''; + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Status Pengaduan', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + const SizedBox(height: 12), + _buildStatusGuideItem( + status, + _getStatusDescription(status), + _getStatusColor(status), + _getStatusIcon(status), + ), + ], + ), + ); + } + + Widget _buildStatusGuideItem( + String status, + String description, + Color color, + IconData icon, + ) { + return Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: color.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Center( + child: Icon( + icon, + color: color, + size: 18, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusPill( + status: _getStatusText(status), + backgroundColor: color, + textColor: Colors.white, + ), + const SizedBox(height: 4), + Text( + description, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade700, + ), + ), + ], + ), + ), + ], + ); + } + + String _getStatusText(String status) { + switch (status) { + case 'MENUNGGU': + return 'Menunggu'; + case 'TINDAKAN': + return 'Tindakan'; + case 'SELESAI': + return 'Selesai'; + default: + return status; + } + } + + String _getStatusDescription(String status) { + switch (status) { + case 'MENUNGGU': + return 'Pengaduan Anda sedang menunggu tindakan dari petugas'; + case 'TINDAKAN': + return 'Pengaduan Anda sedang dalam proses penanganan'; + case 'SELESAI': + return 'Pengaduan Anda telah selesai ditangani'; + default: + return 'Status pengaduan tidak diketahui'; + } + } + + Color _getStatusColor(String status) { + switch (status) { + case 'MENUNGGU': + return Colors.orange; + case 'TINDAKAN': + return Colors.blue; + case 'SELESAI': + return Colors.green; + default: + return Colors.grey; + } + } + + IconData _getStatusIcon(String status) { + switch (status) { + case 'MENUNGGU': + return Icons.hourglass_empty; + case 'TINDAKAN': + return Icons.engineering; + case 'SELESAI': + return Icons.check_circle; + default: + return Icons.help_outline; + } + } + Widget _buildPenyaluranInfo(BuildContext context, PengaduanModel pengaduan) { return Card( elevation: 2, @@ -211,14 +596,24 @@ class WargaDetailPengaduanView extends GetView { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Informasi Penyaluran', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Informasi Penyaluran', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppTheme.primaryColor, + ), + ), + Icon( + Icons.inventory, + color: AppTheme.primaryColor, + ), + ], ), - const SizedBox(height: 16), + const Divider(height: 24), _buildInfoRow('Nama Penyaluran', pengaduan.namaPenyaluran), _buildInfoRow('Jenis Bantuan', pengaduan.jenisBantuan), _buildInfoRow('Jumlah Bantuan', pengaduan.jumlahBantuan), @@ -229,29 +624,47 @@ class WargaDetailPengaduanView extends GetView { ); } + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + value, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + } + Widget _buildTindakanTimeline( BuildContext context, List tindakanList, ) { if (tindakanList.isEmpty) { - return Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: const Padding( - padding: EdgeInsets.all(16), - child: Center( - child: Text( - 'Pengaduan Anda sedang menunggu tindakan dari petugas', - style: TextStyle( - fontSize: 14, - fontStyle: FontStyle.italic, - color: Colors.grey, - ), - ), - ), - ), + return InfoCard( + title: 'Belum Ada Tindakan', + description: 'Pengaduan Anda sedang menunggu tindakan dari petugas', + icon: Icons.info_outline, + backgroundColor: Colors.grey.shade50, ); } @@ -265,12 +678,9 @@ class WargaDetailPengaduanView extends GetView { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Riwayat Tindakan', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + SectionHeader( + title: 'Riwayat Tindakan', + padding: EdgeInsets.zero, ), const SizedBox(height: 16), ListView.builder( @@ -345,53 +755,145 @@ class WargaDetailPengaduanView extends GetView { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Header dengan kategori dan status Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - tindakan.kategoriTindakanText, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: dotColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(4), - ), + Expanded( child: Text( - tindakan.statusTindakanText, - style: TextStyle( - color: dotColor, - fontSize: 12, + tindakan.kategoriTindakanText, + style: const TextStyle( fontWeight: FontWeight.bold, + fontSize: 16, ), ), ), + Row( + children: [ + // Menggunakan StatusPill untuk status tindakan + StatusPill( + status: tindakan.statusTindakanText, + backgroundColor: dotColor, + textColor: Colors.white, + ), + ], + ), ], ), const SizedBox(height: 8), + + // Deskripsi tindakan Text( tindakan.tindakan ?? '', style: const TextStyle(fontSize: 14), ), - if (tindakan.hasilTindakan != null && - tindakan.hasilTindakan!.isNotEmpty) ...[ + + // Catatan tindakan (jika ada) + if (tindakan.catatan != null && tindakan.catatan!.isNotEmpty) ...[ const SizedBox(height: 8), Text( - 'Hasil: ${tindakan.hasilTindakan}', + 'Catatan: ${tindakan.catatan}', style: TextStyle( fontSize: 12, color: Colors.grey.shade700, + fontStyle: FontStyle.italic, ), ), ], + + // Hasil tindakan (jika ada) + if (tindakan.hasilTindakan != null && + tindakan.hasilTindakan!.isNotEmpty) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.blue.shade100), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.check_circle_outline, + size: 16, + color: Colors.blue.shade700, + ), + const SizedBox(width: 4), + Text( + 'Hasil Tindakan:', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + color: Colors.blue.shade700, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + tindakan.hasilTindakan!, + style: TextStyle( + fontSize: 12, + color: Colors.blue.shade900, + ), + ), + ], + ), + ), + ], + + // Bukti tindakan (jika ada) + if (tindakan.buktiTindakan != null && + tindakan.buktiTindakan!.isNotEmpty) ...[ + const SizedBox(height: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Bukti Tindakan:', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Colors.grey.shade800, + ), + ), + const SizedBox(height: 8), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: tindakan.buktiTindakan!.map((bukti) { + return GestureDetector( + onTap: () => showFullScreenImage(context, bukti), + child: Container( + width: 80, + height: 80, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + image: bukti.startsWith('http') + ? NetworkImage(bukti) + : FileImage(File(bukti)) as ImageProvider, + fit: BoxFit.cover, + ), + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ], + const SizedBox(height: 8), + + // Footer dengan info petugas dan tanggal Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -404,7 +906,7 @@ class WargaDetailPengaduanView extends GetView { ), Text( tindakan.tanggalTindakan != null - ? DateFormat('dd MMM yyyy', 'id_ID') + ? DateFormat('dd MMM yyyy HH:mm', 'id_ID') .format(tindakan.tanggalTindakan!) : '-', style: TextStyle( @@ -414,37 +916,166 @@ class WargaDetailPengaduanView extends GetView { ), ], ), + + // Prioritas tindakan (jika ada) + if (tindakan.prioritas != null) ...[ + const SizedBox(height: 8), + // Menggunakan StatusPill untuk prioritas tindakan + StatusPill( + status: tindakan.prioritasText, + backgroundColor: _getPriorityColor(tindakan.prioritas), + textColor: Colors.white, + ), + ], ], ), ), ); } - Widget _buildInfoRow(String label, String value) { - return Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 120, - child: Text( - '$label:', - style: TextStyle( - fontWeight: FontWeight.w500, - color: Colors.grey.shade700, + Color _getPriorityColor(String? priority) { + switch (priority) { + case 'TINGGI': + return Colors.red; + case 'SEDANG': + return Colors.orange; + case 'RENDAH': + return Colors.green; + default: + return Colors.grey; + } + } + + void showFullScreenImage(BuildContext context, String imageUrl) { + // Buat controller untuk InteractiveViewer + final TransformationController transformationController = + TransformationController(); + + Get.dialog( + Dialog( + insetPadding: EdgeInsets.zero, + child: Stack( + fit: StackFit.expand, + children: [ + InteractiveViewer( + panEnabled: true, + minScale: 0.5, + maxScale: 4, + transformationController: transformationController, + child: imageUrl.startsWith('http') + ? Image.network( + imageUrl, + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.grey.shade300, + child: const Center( + child: Icon( + Icons.error, + size: 50, + color: Colors.red, + ), + ), + ); + }, + ) + : Image.file( + File(imageUrl), + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.grey.shade300, + child: const Center( + child: Icon( + Icons.error, + size: 50, + color: Colors.red, + ), + ), + ); + }, + ), + ), + Positioned( + top: 20, + right: 20, + child: GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.close, + color: Colors.white, + ), + ), ), ), - ), - Expanded( - child: Text( - value, - style: const TextStyle( - color: Colors.black87, + Positioned( + bottom: 20, + right: 20, + child: Row( + children: [ + GestureDetector( + onTap: () { + // Zoom in + final Matrix4 matrix = + transformationController.value.clone(); + matrix.scale(1.5); + transformationController.value = matrix; + }, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.zoom_in, + color: Colors.white, + ), + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () { + // Zoom out + final Matrix4 matrix = + transformationController.value.clone(); + matrix.scale(0.75); + transformationController.value = matrix; + }, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.zoom_out, + color: Colors.white, + ), + ), + ), + ], ), ), - ), - ], + ], + ), ), ); } @@ -898,55 +1529,8 @@ class _TambahTindakanPengaduanViewState } void _showFullScreenImage(BuildContext context, String imagePath) { - Get.dialog( - Dialog( - insetPadding: EdgeInsets.zero, - child: Stack( - fit: StackFit.expand, - children: [ - InteractiveViewer( - panEnabled: true, - minScale: 0.5, - maxScale: 4, - child: Image.file( - File(imagePath), - fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) { - return Container( - color: Colors.grey.shade300, - child: const Center( - child: Icon( - Icons.error, - size: 50, - color: Colors.red, - ), - ), - ); - }, - ), - ), - Positioned( - top: 20, - right: 20, - child: GestureDetector( - onTap: () => Get.back(), - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), - shape: BoxShape.circle, - ), - child: const Icon( - Icons.close, - color: Colors.white, - ), - ), - ), - ), - ], - ), - ), - ); + final wargaDetailView = Get.find(); + wargaDetailView.showFullScreenImage(context, imagePath); } Future _simpanTindakan() async { diff --git a/lib/app/services/supabase_service.dart b/lib/app/services/supabase_service.dart index b93a249..5305c65 100644 --- a/lib/app/services/supabase_service.dart +++ b/lib/app/services/supabase_service.dart @@ -811,6 +811,36 @@ class SupabaseService extends GetxService { } } + // Metode untuk menambahkan feedback dan rating pengaduan + Future addPengaduanFeedback( + String pengaduanId, String feedback, int rating) async { + try { + await client.from('pengaduan').update({ + 'feedback_warga': feedback, + 'rating_warga': rating, + 'updated_at': DateTime.now().toIso8601String(), + }).eq('id', pengaduanId); + } catch (e) { + print('Error adding pengaduan feedback: $e'); + throw e.toString(); + } + } + + // Metode untuk memperbarui feedback dan rating pengaduan + Future updatePengaduanFeedback( + String pengaduanId, String feedback, int rating) async { + try { + await client.from('pengaduan').update({ + 'feedback_warga': feedback, + 'rating_warga': rating, + 'updated_at': DateTime.now().toIso8601String(), + }).eq('id', pengaduanId); + } catch (e) { + print('Error updating pengaduan feedback: $e'); + throw e.toString(); + } + } + // Penerima bantuan methods Future>?> getPenerimaBantuan() async { try {