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.

This commit is contained in:
Khafidh Fuadi
2025-03-16 17:20:18 +07:00
parent dfc6080e77
commit a3798f0005
15 changed files with 1587 additions and 48 deletions

View File

@ -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  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>

}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2~
}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2~
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD>
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p
n
lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t
lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t
r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2y
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2y
w
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2z
x
x
vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 ~
|
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
<EFBFBD>
<EFBFBD>
<EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2w
u
u
sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2|
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2|

View File

@ -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  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD>
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
~
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2r
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2r
p
nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2v
nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2v
t
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2{
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2{
y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2|
z
z
xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 <09>
~
~
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
<EFBFBD>
<EFBFBD>
<EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2y
w
w
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2~
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2~

View File

@ -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  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{
y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2x
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2x
v
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2}
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2}
{
yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j
yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j
h
fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n
fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n
l
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2s
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2s
q
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2t
r
r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 x
v
v
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
~
|
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2q
o
o
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2v
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2v

View File

@ -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  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2~
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2~
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2{
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2{
y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
~
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2m
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2m
k
iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2q
iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2q
o
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2v
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2v
t
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2w
u
u
sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 {
y
y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
<EFBFBD>


}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2t
r
r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2y
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2y

View File

@ -0,0 +1 @@

View File

@ -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>(
() => WargaDashboardController(),
);
}
}

View File

@ -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<AuthController>();
final Rx<UserModel?> currentUser = Rx<UserModel?>(null);
// Indeks tab yang aktif di bottom navigation bar
final RxInt activeTabIndex = 0.obs;
// Data untuk summary penerima penyaluran
final RxList<PenerimaPenyaluranModel> penerimaPenyaluran =
<PenerimaPenyaluranModel>[].obs;
final RxInt totalPenyaluranDiterima = 0.obs;
// Data untuk daftar pengajuan kelayakan bantuan
final RxList<PengajuanKelayakanBantuanModel> pengajuanKelayakan =
<PengajuanKelayakanBantuanModel>[].obs;
final RxInt totalPengajuanMenunggu = 0.obs;
final RxInt totalPengajuanTerverifikasi = 0.obs;
final RxInt totalPengajuanDitolak = 0.obs;
// Data untuk summary pengaduan
final RxList<PengaduanModel> pengaduan = <PengaduanModel>[].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();
}
}

View File

@ -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<WargaDashboardController> {
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),
),
),
],
),
),
),
],
);
}
}

View File

@ -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<WargaDashboardController> {
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'),
),
],
),
],
),
),
),
);
},
);
}
}

View File

@ -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<WargaDashboardController> {
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'),
),
],
),
],
),
),
),
);
},
);
}
}

View File

@ -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<WargaDashboardController> {
const WargaView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
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();
}),
);
}
}

View File

@ -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<WargaDashboardController>();
controller.activeTabIndex.value = 1;
return const WargaView();
},
binding: WargaBinding(),
),
GetPage(
name: _Paths.wargaPengaduan,
page: () {
final controller = Get.find<WargaDashboardController>();
controller.activeTabIndex.value = 2;
return const WargaView();
},
binding: WargaBinding(),
),
GetPage(
name: _Paths.petugasDesaDashboard,
page: () => const PetugasDesaView(),

View File

@ -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';

View File

@ -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<AppBottomNavigationBarItem> 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,
});
}

View File

@ -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<DrawerMenuItem> 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,
});
}