From a3798f000556fa22a1e8cd2a1720cf4e56fc238c Mon Sep 17 00:00:00 2001 From: Khafidh Fuadi Date: Sun, 16 Mar 2025 17:20:18 +0700 Subject: [PATCH] Tambahkan rute baru untuk modul Warga, termasuk WargaDashboard, WargaPenyaluran, dan WargaPengaduan. Perbarui referensi import di app_pages.dart dan app_routes.dart untuk mencerminkan perubahan ini, meningkatkan struktur dan navigasi aplikasi. --- .../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 +- .../penyaluran/detail_penyaluran_page.dart | 1 + .../modules/warga/bindings/warga_binding.dart | 11 + .../warga_dashboard_controller.dart | 172 +++++++ .../warga/views/warga_dashboard_view.dart | 487 ++++++++++++++++++ .../warga/views/warga_pengaduan_view.dart | 194 +++++++ .../warga/views/warga_penyaluran_view.dart | 187 +++++++ lib/app/modules/warga/views/warga_view.dart | 180 +++++++ lib/app/routes/app_pages.dart | 29 ++ lib/app/routes/app_routes.dart | 4 + .../widgets/app_bottom_navigation_bar.dart | 112 ++++ lib/app/widgets/app_drawer.dart | 162 ++++++ 15 files changed, 1587 insertions(+), 48 deletions(-) create mode 100644 lib/app/modules/penyaluran/detail_penyaluran_page.dart create mode 100644 lib/app/modules/warga/bindings/warga_binding.dart create mode 100644 lib/app/modules/warga/controllers/warga_dashboard_controller.dart create mode 100644 lib/app/modules/warga/views/warga_dashboard_view.dart create mode 100644 lib/app/modules/warga/views/warga_pengaduan_view.dart create mode 100644 lib/app/modules/warga/views/warga_penyaluran_view.dart create mode 100644 lib/app/modules/warga/views/warga_view.dart create mode 100644 lib/app/widgets/app_bottom_navigation_bar.dart create mode 100644 lib/app/widgets/app_drawer.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 476d84c..42a25c0 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 af6d5e7..4a8da5a 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 85f5535..224641d 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 a02f559..cdc51cb 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/penyaluran/detail_penyaluran_page.dart b/lib/app/modules/penyaluran/detail_penyaluran_page.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/app/modules/penyaluran/detail_penyaluran_page.dart @@ -0,0 +1 @@ + diff --git a/lib/app/modules/warga/bindings/warga_binding.dart b/lib/app/modules/warga/bindings/warga_binding.dart new file mode 100644 index 0000000..049811e --- /dev/null +++ b/lib/app/modules/warga/bindings/warga_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; + +class WargaBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => WargaDashboardController(), + ); + } +} diff --git a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart new file mode 100644 index 0000000..c93991a --- /dev/null +++ b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart @@ -0,0 +1,172 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/data/models/penerima_penyaluran_model.dart'; +import 'package:penyaluran_app/app/data/models/pengaduan_model.dart'; +import 'package:penyaluran_app/app/data/models/pengajuan_kelayakan_bantuan_model.dart'; +import 'package:penyaluran_app/app/data/models/user_model.dart'; +import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; + +class WargaDashboardController extends GetxController { + final AuthController _authController = Get.find(); + + final Rx currentUser = Rx(null); + + // Indeks tab yang aktif di bottom navigation bar + final RxInt activeTabIndex = 0.obs; + + // Data untuk summary penerima penyaluran + final RxList penerimaPenyaluran = + [].obs; + final RxInt totalPenyaluranDiterima = 0.obs; + + // Data untuk daftar pengajuan kelayakan bantuan + final RxList pengajuanKelayakan = + [].obs; + final RxInt totalPengajuanMenunggu = 0.obs; + final RxInt totalPengajuanTerverifikasi = 0.obs; + final RxInt totalPengajuanDitolak = 0.obs; + + // Data untuk summary pengaduan + final RxList pengaduan = [].obs; + final RxInt totalPengaduan = 0.obs; + final RxInt totalPengaduanProses = 0.obs; + final RxInt totalPengaduanSelesai = 0.obs; + + // Indikator loading + final RxBool isLoading = false.obs; + + // Jumlah notifikasi belum dibaca + final RxInt jumlahNotifikasiBelumDibaca = 0.obs; + + // Getter untuk data user + UserModel? get user => _authController.user; + String get role => user?.role ?? 'WARGA'; + String get nama => user?.name ?? 'Warga'; + String? get desa => user?.desa?.nama; + + @override + void onInit() { + super.onInit(); + fetchData(); + loadUserData(); + } + + void loadUserData() { + currentUser.value = _authController.user; + } + + void fetchData() async { + isLoading.value = true; + + try { + // TODO: Implementasi fetch data dari API + // Contoh data dummy untuk pengembangan UI + await Future.delayed(const Duration(seconds: 1)); + + // Dummy data penerima penyaluran + penerimaPenyaluran.value = [ + PenerimaPenyaluranModel( + id: '1', + statusPenerimaan: 'DITERIMA', + tanggalPenerimaan: DateTime.now().subtract(const Duration(days: 5)), + jumlahBantuan: 50000, + keterangan: 'Bantuan Tunai', + ), + PenerimaPenyaluranModel( + id: '2', + statusPenerimaan: 'DITERIMA', + tanggalPenerimaan: DateTime.now().subtract(const Duration(days: 15)), + jumlahBantuan: 100000, + keterangan: 'Bantuan Sembako', + ), + ]; + + totalPenyaluranDiterima.value = penerimaPenyaluran.length; + + // Dummy data pengajuan kelayakan + pengajuanKelayakan.value = [ + PengajuanKelayakanBantuanModel( + id: '1', + status: StatusKelayakan.MENUNGGU, + createdAt: DateTime.now().subtract(const Duration(days: 2)), + ), + PengajuanKelayakanBantuanModel( + id: '2', + status: StatusKelayakan.TERVERIFIKASI, + createdAt: DateTime.now().subtract(const Duration(days: 10)), + ), + PengajuanKelayakanBantuanModel( + id: '3', + status: StatusKelayakan.DITOLAK, + createdAt: DateTime.now().subtract(const Duration(days: 20)), + alasanVerifikasi: 'Dokumen tidak lengkap', + ), + ]; + + totalPengajuanMenunggu.value = pengajuanKelayakan + .where((p) => p.status == StatusKelayakan.MENUNGGU) + .length; + totalPengajuanTerverifikasi.value = pengajuanKelayakan + .where((p) => p.status == StatusKelayakan.TERVERIFIKASI) + .length; + totalPengajuanDitolak.value = pengajuanKelayakan + .where((p) => p.status == StatusKelayakan.DITOLAK) + .length; + + // Dummy data pengaduan + pengaduan.value = [ + PengaduanModel( + id: '1', + judul: 'Bantuan tidak sesuai', + status: 'PROSES', + tanggalPengaduan: DateTime.now().subtract(const Duration(days: 3)), + ), + PengaduanModel( + id: '2', + judul: 'Keterlambatan penyaluran', + status: 'SELESAI', + tanggalPengaduan: DateTime.now().subtract(const Duration(days: 25)), + ), + ]; + + totalPengaduan.value = pengaduan.length; + totalPengaduanProses.value = + pengaduan.where((p) => p.status == 'PROSES').length; + totalPengaduanSelesai.value = + pengaduan.where((p) => p.status == 'SELESAI').length; + + // Dummy data notifikasi + jumlahNotifikasiBelumDibaca.value = 3; + } catch (e) { + print('Error fetching data: $e'); + } finally { + isLoading.value = false; + } + } + + // Navigasi ke halaman detail + void goToPenyaluranDetail() { + changeTab(1); + } + + void goToPengajuanDetail() { + // TODO: Implementasi navigasi ke halaman detail pengajuan + // Untuk saat ini, belum ada halaman detail pengajuan + } + + void goToPengaduanDetail() { + changeTab(2); + } + + // Fungsi untuk mengubah tab + void changeTab(int index) { + activeTabIndex.value = index; + + // Tidak perlu navigasi ke halaman lain, cukup ubah indeks tab + // yang akan mengubah konten body di WargaView + } + + // Fungsi untuk logout + void logout() { + _authController.logout(); + } +} diff --git a/lib/app/modules/warga/views/warga_dashboard_view.dart b/lib/app/modules/warga/views/warga_dashboard_view.dart new file mode 100644 index 0000000..5d3e40a --- /dev/null +++ b/lib/app/modules/warga/views/warga_dashboard_view.dart @@ -0,0 +1,487 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; +import 'package:penyaluran_app/app/data/models/pengajuan_kelayakan_bantuan_model.dart'; + +class WargaDashboardView extends GetView { + const WargaDashboardView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + return RefreshIndicator( + onRefresh: () async { + controller.fetchData(); + }, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildWelcomeSection(), + const SizedBox(height: 24), + _buildPenyaluranSummary(), + const SizedBox(height: 24), + _buildPengajuanSection(), + const SizedBox(height: 24), + _buildPengaduanSummary(), + ], + ), + ), + ); + }); + } + + Widget _buildWelcomeSection() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + CircleAvatar( + radius: 30, + backgroundColor: Colors.blue.shade100, + child: const Icon( + Icons.person, + size: 40, + color: Colors.blue, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Selamat Datang,', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + const SizedBox(height: 4), + Text( + controller.nama, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + controller.desa ?? 'Warga Desa', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildPenyaluranSummary() { + final currencyFormat = NumberFormat.currency( + locale: 'id', + symbol: 'Rp ', + decimalDigits: 0, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Penyaluran Bantuan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Total Bantuan Diterima', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + const SizedBox(height: 8), + Text( + '${controller.totalPenyaluranDiterima.value}', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + ], + ), + const Icon( + Icons.volunteer_activism, + size: 40, + color: Colors.blue, + ), + ], + ), + const Divider(height: 32), + if (controller.penerimaPenyaluran.isNotEmpty) + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: controller.penerimaPenyaluran.length > 2 + ? 2 + : controller.penerimaPenyaluran.length, + itemBuilder: (context, index) { + final item = controller.penerimaPenyaluran[index]; + return ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + item.keterangan ?? 'Bantuan', + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + item.tanggalPenerimaan != null + ? DateFormat('dd MMMM yyyy', 'id_ID') + .format(item.tanggalPenerimaan!) + : '-', + ), + trailing: Text( + item.jumlahBantuan != null + ? currencyFormat.format(item.jumlahBantuan) + : '-', + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green, + ), + ), + ); + }, + ) + else + const Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0), + child: Text('Belum ada penyaluran bantuan'), + ), + ), + if (controller.penerimaPenyaluran.length > 2) + TextButton( + onPressed: () => controller.changeTab(1), + child: const Text('Lihat Semua'), + ), + ], + ), + ), + ), + ], + ); + } + + Widget _buildPengajuanSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pengajuan Kelayakan Bantuan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildStatusCounter( + 'Menunggu', + controller.totalPengajuanMenunggu.value, + Colors.orange, + ), + _buildStatusCounter( + 'Terverifikasi', + controller.totalPengajuanTerverifikasi.value, + Colors.green, + ), + _buildStatusCounter( + 'Ditolak', + controller.totalPengajuanDitolak.value, + Colors.red, + ), + ], + ), + const Divider(height: 32), + if (controller.pengajuanKelayakan.isNotEmpty) + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: controller.pengajuanKelayakan.length > 3 + ? 3 + : controller.pengajuanKelayakan.length, + itemBuilder: (context, index) { + final item = controller.pengajuanKelayakan[index]; + return ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + 'Pengajuan #${index + 1}', + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + item.createdAt != null + ? DateFormat('dd MMMM yyyy', 'id_ID') + .format(item.createdAt!) + : '-', + ), + trailing: _buildStatusBadge(item.status), + ); + }, + ) + else + const Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0), + child: Text('Belum ada pengajuan kelayakan'), + ), + ), + if (controller.pengajuanKelayakan.length > 3) + TextButton( + onPressed: controller.goToPengajuanDetail, + child: const Text('Lihat Semua'), + ), + ], + ), + ), + ), + ], + ); + } + + Widget _buildStatusCounter(String label, int count, Color color) { + return Column( + children: [ + Text( + '$count', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: color, + ), + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ); + } + + Widget _buildStatusBadge(StatusKelayakan? status) { + if (status == null) return const SizedBox(); + + Color color; + String text; + + switch (status) { + case StatusKelayakan.MENUNGGU: + color = Colors.orange; + text = 'Menunggu'; + break; + case StatusKelayakan.TERVERIFIKASI: + color = Colors.green; + text = 'Terverifikasi'; + break; + case StatusKelayakan.DITOLAK: + color = Colors.red; + text = 'Ditolak'; + break; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color), + ), + child: Text( + text, + style: TextStyle( + color: color, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ); + } + + Widget _buildPengaduanSummary() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pengaduan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildStatusCounter( + 'Total', + controller.totalPengaduan.value, + Colors.blue, + ), + _buildStatusCounter( + 'Proses', + controller.totalPengaduanProses.value, + Colors.orange, + ), + _buildStatusCounter( + 'Selesai', + controller.totalPengaduanSelesai.value, + Colors.green, + ), + ], + ), + const Divider(height: 32), + if (controller.pengaduan.isNotEmpty) + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: controller.pengaduan.length > 2 + ? 2 + : controller.pengaduan.length, + itemBuilder: (context, index) { + final item = controller.pengaduan[index]; + return ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + item.judul ?? 'Pengaduan #${index + 1}', + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + item.tanggalPengaduan != null + ? DateFormat('dd MMMM yyyy', 'id_ID') + .format(item.tanggalPengaduan!) + : '-', + ), + trailing: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: item.status == 'PROSES' + ? Colors.orange.withOpacity(0.1) + : Colors.green.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: item.status == 'PROSES' + ? Colors.orange + : Colors.green, + ), + ), + child: Text( + item.status == 'PROSES' ? 'Proses' : 'Selesai', + style: TextStyle( + color: item.status == 'PROSES' + ? Colors.orange + : Colors.green, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ); + }, + ) + else + const Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0), + child: Text('Belum ada pengaduan'), + ), + ), + if (controller.pengaduan.length > 2) + TextButton( + onPressed: () => controller.changeTab(2), + child: const Text('Lihat Semua'), + ), + const SizedBox(height: 8), + ElevatedButton.icon( + onPressed: () { + // TODO: Implementasi navigasi ke halaman buat pengaduan + }, + icon: const Icon(Icons.add), + label: const Text('Buat Pengaduan Baru'), + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 48), + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/app/modules/warga/views/warga_pengaduan_view.dart b/lib/app/modules/warga/views/warga_pengaduan_view.dart new file mode 100644 index 0000000..bdd82d3 --- /dev/null +++ b/lib/app/modules/warga/views/warga_pengaduan_view.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; + +class WargaPengaduanView extends GetView { + const WargaPengaduanView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + return RefreshIndicator( + onRefresh: () async { + controller.fetchData(); + }, + child: controller.pengaduan.isEmpty + ? _buildEmptyState() + : _buildPengaduanList(), + ); + }); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.report_problem, + size: 80, + color: Colors.grey.shade400, + ), + const SizedBox(height: 16), + Text( + 'Belum Ada Pengaduan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey.shade700, + ), + ), + const SizedBox(height: 8), + Text( + 'Anda belum membuat pengaduan', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: () { + // TODO: Implementasi navigasi ke halaman buat pengaduan + Get.toNamed('/buat-pengaduan'); + }, + icon: const Icon(Icons.add), + label: const Text('Buat Pengaduan Baru'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + ), + ), + ], + ), + ); + } + + Widget _buildPengaduanList() { + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: controller.pengaduan.length, + itemBuilder: (context, index) { + final item = controller.pengaduan[index]; + final isProses = item.status == 'PROSES'; + + return Card( + margin: const EdgeInsets.only(bottom: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 2, + child: InkWell( + onTap: () { + // TODO: Navigasi ke detail pengaduan + }, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + item.judul ?? 'Pengaduan #${index + 1}', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: isProses + ? Colors.orange.withOpacity(0.1) + : Colors.green.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: isProses ? Colors.orange : Colors.green, + ), + ), + child: Text( + isProses ? 'Proses' : 'Selesai', + style: TextStyle( + color: isProses ? Colors.orange : Colors.green, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + if (item.deskripsi != null && item.deskripsi!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Text( + item.deskripsi!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Colors.grey.shade700, + ), + ), + ), + Row( + children: [ + Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Text( + item.tanggalPengaduan != null + ? DateFormat('dd MMMM yyyy', 'id_ID') + .format(item.tanggalPengaduan!) + : '-', + style: TextStyle( + color: Colors.grey.shade600, + ), + ), + ], + ), + const SizedBox(height: 16), + const Divider(height: 1), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + onPressed: () { + // TODO: Implementasi navigasi ke detail pengaduan + Get.toNamed('/detail-pengaduan', + arguments: {'id': item.id}); + }, + icon: const Icon(Icons.visibility), + label: const Text('Lihat Detail'), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/app/modules/warga/views/warga_penyaluran_view.dart b/lib/app/modules/warga/views/warga_penyaluran_view.dart new file mode 100644 index 0000000..f266523 --- /dev/null +++ b/lib/app/modules/warga/views/warga_penyaluran_view.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; + +class WargaPenyaluranView extends GetView { + const WargaPenyaluranView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + return RefreshIndicator( + onRefresh: () async { + controller.fetchData(); + }, + child: controller.penerimaPenyaluran.isEmpty + ? _buildEmptyState() + : _buildPenyaluranList(), + ); + }); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.volunteer_activism, + size: 80, + color: Colors.grey.shade400, + ), + const SizedBox(height: 16), + Text( + 'Belum Ada Penyaluran', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey.shade700, + ), + ), + const SizedBox(height: 8), + Text( + 'Anda belum menerima penyaluran bantuan', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ); + } + + Widget _buildPenyaluranList() { + final currencyFormat = NumberFormat.currency( + locale: 'id', + symbol: 'Rp ', + decimalDigits: 0, + ); + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: controller.penerimaPenyaluran.length, + itemBuilder: (context, index) { + final item = controller.penerimaPenyaluran[index]; + return Card( + margin: const EdgeInsets.only(bottom: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 2, + child: InkWell( + onTap: () { + // TODO: Navigasi ke detail penyaluran + }, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + item.keterangan ?? 'Bantuan', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.green), + ), + child: const Text( + 'Diterima', + style: TextStyle( + color: Colors.green, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Icon( + Icons.calendar_today, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Text( + item.tanggalPenerimaan != null + ? DateFormat('dd MMMM yyyy', 'id_ID') + .format(item.tanggalPenerimaan!) + : '-', + style: TextStyle( + color: Colors.grey.shade600, + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.attach_money, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Text( + item.jumlahBantuan != null + ? currencyFormat.format(item.jumlahBantuan) + : '-', + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green, + ), + ), + ], + ), + const SizedBox(height: 16), + const Divider(height: 1), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + onPressed: () { + // TODO: Implementasi navigasi ke detail penyaluran + Get.toNamed('/detail-penyaluran', + arguments: {'id': item.id}); + }, + icon: const Icon(Icons.visibility), + label: const Text('Lihat Detail'), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/app/modules/warga/views/warga_view.dart b/lib/app/modules/warga/views/warga_view.dart new file mode 100644 index 0000000..637f7a7 --- /dev/null +++ b/lib/app/modules/warga/views/warga_view.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; +import 'package:penyaluran_app/app/modules/warga/views/warga_dashboard_view.dart'; +import 'package:penyaluran_app/app/modules/warga/views/warga_penyaluran_view.dart'; +import 'package:penyaluran_app/app/modules/warga/views/warga_pengaduan_view.dart'; +import 'package:penyaluran_app/app/widgets/app_drawer.dart'; +import 'package:penyaluran_app/app/widgets/app_bottom_navigation_bar.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class WargaView extends GetView { + const WargaView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final GlobalKey scaffoldKey = GlobalKey(); + + return Scaffold( + key: scaffoldKey, + appBar: AppBar( + title: Obx(() { + switch (controller.activeTabIndex.value) { + case 0: + return const Text('Dashboard Warga'); + case 1: + return const Text('Penyaluran Bantuan'); + case 2: + return const Text('Pengaduan'); + default: + return const Text('Dashboard Warga'); + } + }), + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () { + scaffoldKey.currentState?.openDrawer(); + }, + ), + actions: [ + // Tombol notifikasi + Stack( + alignment: Alignment.center, + children: [ + IconButton( + icon: const Icon(Icons.notifications_outlined), + onPressed: () { + // Navigasi ke halaman notifikasi + Get.toNamed('/notifikasi'); + }, + ), + Obx(() { + if (controller.jumlahNotifikasiBelumDibaca.value > 0) { + return Positioned( + top: 8, + right: 8, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(10), + ), + constraints: const BoxConstraints( + minWidth: 16, + minHeight: 16, + ), + child: Text( + controller.jumlahNotifikasiBelumDibaca.value.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 10, + ), + textAlign: TextAlign.center, + ), + ), + ); + } else { + return const SizedBox.shrink(); + } + }), + ], + ), + ], + ), + drawer: Obx(() => AppDrawer( + nama: controller.nama, + role: 'Warga', + desa: controller.desa, + notificationCount: controller.jumlahNotifikasiBelumDibaca.value, + onLogout: controller.logout, + menuItems: [ + DrawerMenuItem( + icon: Icons.dashboard_outlined, + title: 'Dashboard', + isSelected: controller.activeTabIndex.value == 0, + onTap: () => controller.changeTab(0), + ), + DrawerMenuItem( + icon: Icons.volunteer_activism_outlined, + title: 'Penyaluran', + isSelected: controller.activeTabIndex.value == 1, + onTap: () => controller.changeTab(1), + ), + DrawerMenuItem( + icon: Icons.report_problem_outlined, + title: 'Pengaduan', + isSelected: controller.activeTabIndex.value == 2, + badgeCount: controller.totalPengaduanProses.value, + badgeColor: Colors.orange, + onTap: () => controller.changeTab(2), + ), + DrawerMenuItem( + icon: Icons.assignment_outlined, + title: 'Pengajuan Kelayakan', + onTap: () { + // TODO: Navigasi ke halaman pengajuan kelayakan + Get.toNamed('/pengajuan-kelayakan'); + }, + badgeCount: controller.totalPengajuanMenunggu.value, + badgeColor: Colors.blue, + ), + ], + )), + body: Obx(() { + switch (controller.activeTabIndex.value) { + case 0: + return const WargaDashboardView(); + case 1: + return const WargaPenyaluranView(); + case 2: + return const WargaPengaduanView(); + default: + return const WargaDashboardView(); + } + }), + bottomNavigationBar: Obx(() => AppBottomNavigationBar( + currentIndex: controller.activeTabIndex.value, + onTap: controller.changeTab, + items: [ + AppBottomNavigationBarItem( + icon: Icons.dashboard_outlined, + activeIcon: Icons.dashboard, + label: 'Beranda', + ), + AppBottomNavigationBarItem( + icon: Icons.volunteer_activism_outlined, + activeIcon: Icons.volunteer_activism, + label: 'Penyaluran', + badgeCount: controller.totalPenyaluranDiterima.value > 0 + ? controller.totalPenyaluranDiterima.value + : null, + badgeColor: Colors.green, + ), + AppBottomNavigationBarItem( + icon: Icons.report_problem_outlined, + activeIcon: Icons.report_problem, + label: 'Pengaduan', + badgeCount: controller.totalPengaduanProses.value > 0 + ? controller.totalPengaduanProses.value + : null, + badgeColor: Colors.orange, + ), + ], + )), + floatingActionButton: Obx(() { + // Tampilkan FAB hanya di halaman pengaduan + if (controller.activeTabIndex.value == 2) { + return FloatingActionButton( + onPressed: () { + // TODO: Implementasi navigasi ke halaman buat pengaduan + Get.toNamed('/buat-pengaduan'); + }, + backgroundColor: AppTheme.primaryColor, + child: const Icon(Icons.add), + ); + } + return const SizedBox.shrink(); + }), + ); + } +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index e3c8806..bb7ac86 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -20,6 +20,12 @@ import 'package:penyaluran_app/app/modules/profile/bindings/profile_binding.dart import 'package:penyaluran_app/app/modules/profile/views/profile_view.dart'; import 'package:penyaluran_app/app/modules/splash/bindings/splash_binding.dart'; import 'package:penyaluran_app/app/modules/splash/views/splash_view.dart'; +import 'package:penyaluran_app/app/modules/warga/bindings/warga_binding.dart'; +import 'package:penyaluran_app/app/modules/warga/views/warga_dashboard_view.dart'; +import 'package:penyaluran_app/app/modules/warga/views/warga_penyaluran_view.dart'; +import 'package:penyaluran_app/app/modules/warga/views/warga_pengaduan_view.dart'; +import 'package:penyaluran_app/app/modules/warga/views/warga_view.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; part 'app_routes.dart'; @@ -39,6 +45,29 @@ class AppPages { page: () => const LoginView(), binding: AuthBinding(), ), + GetPage( + name: _Paths.wargaDashboard, + page: () => const WargaView(), + binding: WargaBinding(), + ), + GetPage( + name: _Paths.wargaPenyaluran, + page: () { + final controller = Get.find(); + controller.activeTabIndex.value = 1; + return const WargaView(); + }, + binding: WargaBinding(), + ), + GetPage( + name: _Paths.wargaPengaduan, + page: () { + final controller = Get.find(); + controller.activeTabIndex.value = 2; + return const WargaView(); + }, + binding: WargaBinding(), + ), GetPage( name: _Paths.petugasDesaDashboard, page: () => const PetugasDesaView(), diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index ed05fbe..418e98e 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -6,6 +6,8 @@ abstract class Routes { static const login = _Paths.login; static const register = _Paths.register; static const wargaDashboard = _Paths.wargaDashboard; + static const wargaPenyaluran = _Paths.wargaPenyaluran; + static const wargaPengaduan = _Paths.wargaPengaduan; static const petugasVerifikasiDashboard = _Paths.petugasVerifikasiDashboard; static const petugasDesaDashboard = _Paths.petugasDesaDashboard; static const donaturDashboard = _Paths.donaturDashboard; @@ -33,6 +35,8 @@ abstract class _Paths { static const login = '/login'; static const register = '/register'; static const wargaDashboard = '/warga-dashboard'; + static const wargaPenyaluran = '/warga-penyaluran'; + static const wargaPengaduan = '/warga-pengaduan'; static const petugasVerifikasiDashboard = '/petugas-verifikasi-dashboard'; static const petugasDesaDashboard = '/petugas-desa-dashboard'; static const donaturDashboard = '/donatur-dashboard'; diff --git a/lib/app/widgets/app_bottom_navigation_bar.dart b/lib/app/widgets/app_bottom_navigation_bar.dart new file mode 100644 index 0000000..b8ce113 --- /dev/null +++ b/lib/app/widgets/app_bottom_navigation_bar.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class AppBottomNavigationBar extends StatelessWidget { + final int currentIndex; + final Function(int) onTap; + final List items; + + const AppBottomNavigationBar({ + Key? key, + required this.currentIndex, + required this.onTap, + required this.items, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + currentIndex: currentIndex, + onTap: onTap, + type: BottomNavigationBarType.fixed, + selectedItemColor: AppTheme.primaryColor, + unselectedItemColor: Colors.grey, + items: items.map((item) => _buildNavigationBarItem(item)).toList(), + ); + } + + BottomNavigationBarItem _buildNavigationBarItem( + AppBottomNavigationBarItem item) { + return BottomNavigationBarItem( + icon: item.badgeCount != null && item.badgeCount! > 0 + ? Stack( + alignment: Alignment.center, + children: [ + Icon(item.icon), + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: item.badgeColor ?? Colors.red, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + item.badgeCount.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ) + : Icon(item.icon), + activeIcon: item.badgeCount != null && item.badgeCount! > 0 + ? Stack( + alignment: Alignment.center, + children: [ + Icon(item.activeIcon ?? item.icon), + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: item.badgeColor ?? Colors.red, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + item.badgeCount.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ) + : Icon(item.activeIcon ?? item.icon), + label: item.label, + ); + } +} + +class AppBottomNavigationBarItem { + final IconData icon; + final IconData? activeIcon; + final String label; + final int? badgeCount; + final Color? badgeColor; + + AppBottomNavigationBarItem({ + required this.icon, + this.activeIcon, + required this.label, + this.badgeCount, + this.badgeColor, + }); +} diff --git a/lib/app/widgets/app_drawer.dart b/lib/app/widgets/app_drawer.dart new file mode 100644 index 0000000..bd6da2a --- /dev/null +++ b/lib/app/widgets/app_drawer.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class AppDrawer extends StatelessWidget { + final String nama; + final String role; + final String? desa; + final String? avatar; + final int? notificationCount; + final VoidCallback onLogout; + final List menuItems; + + const AppDrawer({ + Key? key, + required this.nama, + required this.role, + this.desa, + this.avatar, + this.notificationCount, + required this.onLogout, + required this.menuItems, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + decoration: BoxDecoration( + gradient: AppTheme.primaryGradient, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CircleAvatar( + radius: 30, + backgroundColor: Colors.white, + backgroundImage: + avatar != null ? NetworkImage(avatar!) : null, + child: avatar == null + ? const Icon( + Icons.person, + size: 40, + color: AppTheme.primaryColor, + ) + : null, + ), + const SizedBox(height: 10), + Text( + nama, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + desa != null ? '$role - $desa' : role, + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 14, + ), + ), + ], + ), + ), + ...menuItems.map((item) => _buildMenuItem(context, item)), + const Divider(), + ListTile( + leading: const Icon(Icons.person_outline), + title: const Text('Profil'), + onTap: () { + Navigator.pop(context); + Get.toNamed('/profile'); + }, + ), + ListTile( + leading: const Icon(Icons.settings_outlined), + title: const Text('Pengaturan'), + onTap: () { + Navigator.pop(context); + // TODO: Navigasi ke halaman pengaturan + }, + ), + ListTile( + leading: const Icon(Icons.logout), + title: const Text('Keluar'), + onTap: () { + Navigator.pop(context); + onLogout(); + }, + ), + ], + ), + ); + } + + Widget _buildMenuItem(BuildContext context, DrawerMenuItem item) { + return ListTile( + leading: item.badgeCount != null && item.badgeCount! > 0 + ? Stack( + alignment: Alignment.center, + children: [ + Icon(item.icon), + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: item.badgeColor ?? Colors.red, + borderRadius: BorderRadius.circular(10), + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + item.badgeCount.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ) + : Icon(item.icon), + title: Text(item.title), + selected: item.isSelected, + selectedColor: AppTheme.primaryColor, + onTap: () { + Navigator.pop(context); + item.onTap(); + }, + ); + } +} + +class DrawerMenuItem { + final IconData icon; + final String title; + final bool isSelected; + final VoidCallback onTap; + final int? badgeCount; + final Color? badgeColor; + + DrawerMenuItem({ + required this.icon, + required this.title, + this.isSelected = false, + required this.onTap, + this.badgeCount, + this.badgeColor, + }); +}