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:
@ -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|
|
@ -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~
|
@ -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
|
@ -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
|
1
lib/app/modules/penyaluran/detail_penyaluran_page.dart
Normal file
1
lib/app/modules/penyaluran/detail_penyaluran_page.dart
Normal file
@ -0,0 +1 @@
|
||||
|
11
lib/app/modules/warga/bindings/warga_binding.dart
Normal file
11
lib/app/modules/warga/bindings/warga_binding.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
487
lib/app/modules/warga/views/warga_dashboard_view.dart
Normal file
487
lib/app/modules/warga/views/warga_dashboard_view.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
194
lib/app/modules/warga/views/warga_pengaduan_view.dart
Normal file
194
lib/app/modules/warga/views/warga_pengaduan_view.dart
Normal 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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
187
lib/app/modules/warga/views/warga_penyaluran_view.dart
Normal file
187
lib/app/modules/warga/views/warga_penyaluran_view.dart
Normal 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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
180
lib/app/modules/warga/views/warga_view.dart
Normal file
180
lib/app/modules/warga/views/warga_view.dart
Normal 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();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
|
@ -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';
|
||||
|
112
lib/app/widgets/app_bottom_navigation_bar.dart
Normal file
112
lib/app/widgets/app_bottom_navigation_bar.dart
Normal 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,
|
||||
});
|
||||
}
|
162
lib/app/widgets/app_drawer.dart
Normal file
162
lib/app/widgets/app_drawer.dart
Normal 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,
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user