Perbarui model PenitipanBantuan untuk menggunakan stok_bantuan sebagai kategori bantuan. Tambahkan rute baru untuk Donatur dengan beberapa halaman terkait, termasuk donaturDashboard, donaturSkema, donaturJadwal, donaturPenitipan, dan donaturLaporan. Modifikasi rute dan tampilan untuk meningkatkan navigasi dan pengalaman pengguna.

This commit is contained in:
Khafidh Fuadi
2025-03-26 08:33:55 +07:00
parent e881b2e37f
commit 88bef1c8e1
17 changed files with 4233 additions and 53 deletions

View File

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>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><EFBFBD><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>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><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>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><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>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><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>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><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>2y
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  <08><EFBFBD><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>2
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  <08><EFBFBD><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>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><EFBFBD><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>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><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>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><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>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><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>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><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>2{
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  <08><EFBFBD><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>2
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  <08><EFBFBD><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>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><EFBFBD><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>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><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>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><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>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><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>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><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>2s
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  <08><EFBFBD><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>2
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  <08><EFBFBD><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>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2~
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ِ<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>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  ِ<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>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  ِ<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>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  ِ<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>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2q
iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  ِ<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>2v
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  ِ<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>2
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  ِ<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  ِ<><D990><EFBFBD>2 {
y
y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  ِ<><D990><EFBFBD>2
<EFBFBD>


}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  ِ<><D990><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  ِ<><D990><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2y
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2y

View File

@ -78,8 +78,8 @@ class PenitipanBantuanModel {
donatur: json["donatur"] != null
? DonaturModel.fromJson(json["donatur"])
: null,
kategoriBantuan: json["kategori_bantuan"] != null
? KategoriBantuanModel.fromJson(json["kategori_bantuan"])
kategoriBantuan: json["stok_bantuan"] != null
? KategoriBantuanModel.fromJson(json["stok_bantuan"])
: null,
isUang: json["is_uang"] ?? false,
);

View File

@ -0,0 +1,20 @@
import 'package:get/get.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
class DonaturBinding extends Bindings {
@override
void dependencies() {
// Hapus controller lama jika sudah ada
if (Get.isRegistered<DonaturDashboardController>(
tag: 'donatur_dashboard')) {
Get.delete<DonaturDashboardController>(tag: 'donatur_dashboard');
}
// Pasang controller baru
Get.put<DonaturDashboardController>(
DonaturDashboardController(),
permanent: true,
tag: 'donatur_dashboard',
);
}
}

View File

@ -0,0 +1,457 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:penyaluran_app/app/data/models/donatur_model.dart';
import 'package:penyaluran_app/app/data/models/penitipan_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/skema_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/laporan_penyaluran_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/stok_bantuan_model.dart';
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
import 'package:penyaluran_app/app/services/supabase_service.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:supabase_flutter/supabase_flutter.dart';
class DonaturDashboardController extends GetxController {
final AuthController _authController = Get.find<AuthController>();
final SupabaseService _supabaseService = SupabaseService.to;
final Rx<BaseUserModel?> currentUser = Rx<BaseUserModel?>(null);
// Variabel untuk foto profil
final RxString fotoProfil = ''.obs;
// Indeks tab yang aktif di bottom navigation bar
final RxInt activeTabIndex = 0.obs;
// Data untuk skema bantuan tersedia
final RxList<SkemaBantuanModel> skemaBantuan = <SkemaBantuanModel>[].obs;
// Data untuk jadwal penyaluran
final RxList<PenyaluranBantuanModel> jadwalPenyaluran =
<PenyaluranBantuanModel>[].obs;
// Data untuk riwayat penitipan bantuan
final RxList<PenitipanBantuanModel> penitipanBantuan =
<PenitipanBantuanModel>[].obs;
// Data untuk laporan penyaluran
final RxList<LaporanPenyaluranModel> laporanPenyaluran =
<LaporanPenyaluranModel>[].obs;
// Data untuk stok bantuan yang tersedia
final RxList<StokBantuanModel> stokBantuan = <StokBantuanModel>[].obs;
// Indikator loading
final RxBool isLoading = false.obs;
// Jumlah notifikasi belum dibaca
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
// Data untuk foto bantuan pada form penitipan
final RxList<String> fotoBantuanPaths = <String>[].obs;
final ImagePicker _imagePicker = ImagePicker();
// Getter untuk data user
BaseUserModel? get user => _authController.baseUser;
String get role => user?.role ?? 'DONATUR';
String get nama {
// Gunakan namaLengkap dari roleData jika tersedia
if (_authController.isDonatur && _authController.roleData != null) {
return _authController.roleData.namaLengkap ??
_authController.displayName;
}
// Gunakan displayName dari AuthController
return _authController.displayName;
}
String? get desa => user?.desa?.nama;
// Getter untuk alamat dan noHp
String? get alamat {
if (_authController.isDonatur && _authController.roleData != null) {
return (_authController.roleData as DonaturModel).alamat;
}
return null;
}
String? get noHp {
if (_authController.isDonatur && _authController.roleData != null) {
return (_authController.roleData as DonaturModel).noHp;
}
return null;
}
// Getter untuk jenis donatur
String? get jenis {
if (_authController.isDonatur && _authController.roleData != null) {
return (_authController.roleData as DonaturModel).jenis;
}
return null;
}
// Getter untuk foto profil
String? get profilePhotoUrl {
// 1. Coba ambil dari fotoProfil yang sudah disimpan
if (fotoProfil.isNotEmpty) {
return fotoProfil.value;
}
// 2. Coba ambil dari roleData jika merupakan DonaturModel
if (_authController.isDonatur && _authController.roleData != null) {
final donaturData = _authController.roleData as DonaturModel;
if (donaturData.fotoProfil != null &&
donaturData.fotoProfil!.isNotEmpty) {
return donaturData.fotoProfil;
}
}
// 3. Coba ambil dari userData.roleData.fotoProfil
final userData = _authController.userData;
if (userData != null && userData.roleData is DonaturModel) {
final donaturData = userData.roleData as DonaturModel;
if (donaturData.fotoProfil != null &&
donaturData.fotoProfil!.isNotEmpty) {
return donaturData.fotoProfil;
}
}
return null;
}
@override
void onInit() {
super.onInit();
fetchData();
loadUserData();
}
@override
void onReady() {
super.onReady();
// Perbarui data user dan foto profil saat halaman siap
loadUserData();
}
void loadUserData() {
currentUser.value = _authController.baseUser;
if (_authController.userData != null) {
if (_authController.isDonatur) {
var donaturData = _authController.roleData;
// Ambil foto profil dari donaturData jika ada
if (donaturData != null &&
donaturData.fotoProfil != null &&
donaturData.fotoProfil!.isNotEmpty) {
fotoProfil.value = donaturData.fotoProfil!;
}
}
}
// Ambil foto profil dari database
_fetchProfilePhoto();
}
// Metode untuk mengambil foto profil
Future<void> _fetchProfilePhoto() async {
try {
if (user?.id == null) return;
final donaturData = await _supabaseService.client
.from('donatur')
.select('foto_profil')
.eq('id', user!.id)
.maybeSingle();
if (donaturData != null && donaturData['foto_profil'] != null) {
fotoProfil.value = donaturData['foto_profil'];
}
} catch (e) {
print('Error fetching profile photo: $e');
}
}
void fetchData() async {
isLoading.value = true;
try {
// Pastikan user sudah login dan memiliki ID
if (user?.id == null) {
throw Exception('User tidak terautentikasi');
}
// Ambil data skema bantuan
await fetchSkemaBantuan();
// Ambil data jadwal penyaluran
await fetchJadwalPenyaluran();
// Ambil data penitipan bantuan
await fetchPenitipanBantuan();
// Ambil data laporan penyaluran
await fetchLaporanPenyaluran();
// Ambil data stok bantuan
await fetchStokBantuan();
// Ambil data notifikasi
await fetchNotifikasi();
} catch (e) {
print('Error fetching data: $e');
} finally {
isLoading.value = false;
}
}
// Ambil data skema bantuan
Future<void> fetchSkemaBantuan() async {
try {
final response = await _supabaseService.client
.from('xx02_skema_bantuan')
.select()
.order('created_at', ascending: false);
skemaBantuan.value = response
.map((data) => SkemaBantuanModel.fromJson(data))
.toList()
.cast<SkemaBantuanModel>();
} catch (e) {
print('Error fetching skema bantuan: $e');
}
}
// Ambil data jadwal penyaluran
Future<void> fetchJadwalPenyaluran() async {
try {
final now = DateTime.now();
final response = await _supabaseService.client
.from('penyaluran_bantuan')
.select()
.order('tanggal_penyaluran', ascending: true);
// Konversi ke model lalu filter di sisi client
final allJadwal = response
.map((data) => PenyaluranBantuanModel.fromJson(data))
.toList()
.cast<PenyaluranBantuanModel>();
// Filter jadwal yang tanggalnya lebih besar dari hari ini
jadwalPenyaluran.value = allJadwal
.where((jadwal) =>
jadwal.tanggalPenyaluran != null &&
jadwal.tanggalPenyaluran!.isAfter(now))
.toList();
} catch (e) {
print('Error fetching jadwal penyaluran: $e');
}
}
// Ambil data penitipan bantuan
Future<void> fetchPenitipanBantuan() async {
try {
if (user?.id == null) return;
final response = await _supabaseService.client
.from('penitipan_bantuan')
.select('*, donatur(*), stok_bantuan:stok_bantuan_id(*)')
.eq('donatur_id', user!.id)
.order('created_at', ascending: false);
penitipanBantuan.value = response
.map((data) => PenitipanBantuanModel.fromJson(data))
.toList()
.cast<PenitipanBantuanModel>();
} catch (e) {
print('Error fetching penitipan bantuan: $e');
}
}
// Ambil data laporan penyaluran
Future<void> fetchLaporanPenyaluran() async {
try {
final response = await _supabaseService.client
.from('laporan_penyaluran')
.select()
.order('created_at', ascending: false);
laporanPenyaluran.value = response
.map((data) => LaporanPenyaluranModel.fromJson(data))
.toList()
.cast<LaporanPenyaluranModel>();
} catch (e) {
print('Error fetching laporan penyaluran: $e');
}
}
// Ambil data stok bantuan
Future<void> fetchStokBantuan() async {
try {
final response = await _supabaseService.client
.from('stok_bantuan')
.select('*, kategori_bantuan:kategori_bantuan_id(*)')
.order('nama', ascending: true);
stokBantuan.value = response
.map((data) => StokBantuanModel.fromJson(data))
.toList()
.cast<StokBantuanModel>();
} catch (e) {
print('Error fetching stok bantuan: $e');
}
}
// Ambil data notifikasi
Future<void> fetchNotifikasi() async {
try {
if (user?.id == null) return;
final response = await _supabaseService.client
.from('notifikasi')
.select('*')
.eq('user_id', user!.id)
.eq('is_read', false)
.count();
jumlahNotifikasiBelumDibaca.value = response.count ?? 0;
} catch (e) {
print('Error fetching notifikasi: $e');
}
}
// Fungsi untuk logout
void logout() async {
try {
await _authController.logout();
Get.offAllNamed(Routes.login);
} catch (e) {
print('Error during logout: $e');
Get.snackbar(
'Error',
'Terjadi kesalahan saat logout: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
}
// Mendapatkan daftar stok bantuan yang tersedia
List<StokBantuanModel> getAvailableStokBantuan() {
// Filter stok bantuan yang jumlahnya lebih dari 0
return stokBantuan.where((stok) => (stok.totalStok ?? 0) > 0).toList();
}
// Ambil gambar dari kamera atau galeri
Future<void> pickImage({required bool isCamera}) async {
try {
final source = isCamera ? ImageSource.camera : ImageSource.gallery;
final pickedFile = await _imagePicker.pickImage(
source: source,
imageQuality: 70, // Kurangi kualitas untuk menghemat ukuran
);
if (pickedFile != null) {
fotoBantuanPaths.add(pickedFile.path);
}
} catch (e) {
print('Error picking image: $e');
Get.snackbar(
'Gagal',
'Terjadi kesalahan saat mengambil gambar',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
// Hapus foto bantuan dari daftar
void removeFotoBantuan(int index) {
if (index >= 0 && index < fotoBantuanPaths.length) {
fotoBantuanPaths.removeAt(index);
}
}
// Reset foto bantuan paths
void resetFotoBantuan() {
fotoBantuanPaths.clear();
}
// Membuat penitipan bantuan baru
Future<void> createPenitipanBantuan(
String? stokBantuanId,
double jumlah,
String deskripsi,
String? skemaBantuanId,
) async {
try {
isLoading.value = true;
if (user?.id == null) {
throw Exception('User tidak terautentikasi');
}
if (stokBantuanId == null) {
throw Exception('Stok bantuan harus dipilih');
}
if (fotoBantuanPaths.isEmpty) {
throw Exception('Foto bantuan harus diunggah');
}
// Unggah foto bantuan ke storage menggunakan metode dari SupabaseService
final fotoBantuanUrls = await _supabaseService.uploadMultipleFiles(
fotoBantuanPaths, 'penitipan', 'foto_bantuan');
// Data yang akan disimpan
final Map<String, dynamic> data = {
'donatur_id': user!.id,
'stok_bantuan_id': stokBantuanId,
'jumlah': jumlah,
'deskripsi': deskripsi,
'status': 'MENUNGGU',
'tanggal_penitipan': DateTime.now().toIso8601String(),
'foto_bantuan': fotoBantuanUrls,
};
// Tambahkan skema bantuan jika ada
if (skemaBantuanId != null && skemaBantuanId.isNotEmpty) {
data['skema_bantuan_id'] = skemaBantuanId;
}
// Simpan ke database
await _supabaseService.client.from('penitipan_bantuan').insert(data);
// Reset foto bantuan setelah berhasil disimpan
resetFotoBantuan();
// Ambil data penitipan bantuan yang baru
await fetchPenitipanBantuan();
// Tampilkan pesan sukses
Get.snackbar(
'Berhasil',
'Penitipan bantuan berhasil dikirim dan akan diproses oleh petugas desa',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
// Pindah ke tab riwayat penitipan
DefaultTabController.of(Get.context!)?.animateTo(0);
} catch (e) {
print('Error creating penitipan bantuan: $e');
Get.snackbar(
'Gagal',
'Terjadi kesalahan saat mengirim penitipan bantuan: $e',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
} finally {
isLoading.value = false;
}
}
}

View File

@ -0,0 +1,774 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
import 'package:penyaluran_app/app/widgets/section_header.dart';
class DonaturDashboardView extends GetView<DonaturDashboardController> {
const DonaturDashboardView({super.key});
@override
DonaturDashboardController get controller {
if (!Get.isRegistered<DonaturDashboardController>(
tag: 'donatur_dashboard')) {
return Get.put(DonaturDashboardController(),
tag: 'donatur_dashboard', permanent: true);
}
return Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: 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),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildWelcomeSection(),
const SizedBox(height: 24),
_buildStatisticSection(),
const SizedBox(height: 24),
_buildUpcomingEvents(),
const SizedBox(height: 24),
_buildRecentPenitipan(),
],
),
),
);
}),
);
}
Widget _buildWelcomeSection() {
return Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.1),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [Colors.white, Colors.blue.shade50],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.blue.shade200, width: 2),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Hero(
tag: 'donatur-profile',
child: CircleAvatar(
radius: 30,
backgroundColor: Colors.blue.shade100,
backgroundImage: controller.profilePhotoUrl != null
? NetworkImage(controller.profilePhotoUrl!)
: null,
child: controller.profilePhotoUrl == null
? Icon(
Icons.person,
color: Colors.blue.shade700,
size: 30,
)
: null,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Selamat Datang,',
style: TextStyle(
fontSize: 14,
color: Colors.blue.shade700,
fontWeight: FontWeight.w500,
),
),
Text(
controller.nama,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue.shade900,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Column(
children: [
_buildInfoRow(
icon: Icons.home_rounded,
iconColor: Colors.blue.shade300,
label: 'Alamat',
value: controller.alamat ?? 'Alamat tidak tersedia',
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Divider(height: 1),
),
_buildInfoRow(
icon: Icons.phone_rounded,
iconColor: Colors.green.shade300,
label: 'No. HP',
value: controller.noHp ?? 'No. HP tidak tersedia',
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Divider(height: 1),
),
_buildInfoRow(
icon: Icons.category_rounded,
iconColor: Colors.amber.shade300,
label: 'Jenis Donatur',
value: controller.jenis ?? 'Jenis tidak tersedia',
),
],
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildActionButton(
icon: Icons.edit_rounded,
label: 'Edit Profil',
color: Colors.blue.shade700,
onTap: () => Get.toNamed(Routes.profile),
),
),
const SizedBox(width: 10),
Expanded(
child: _buildActionButton(
icon: Icons.add_box_rounded,
label: 'Titip Bantuan',
color: Colors.green.shade700,
onTap: () {
// Navigasi ke form penitipan bantuan
controller.activeTabIndex.value = 3;
},
),
),
],
),
],
),
),
),
);
}
Widget _buildStatisticSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SectionHeader(
title: 'Statistik Kontribusi',
),
Text(
'Ringkasan aktivitas anda sebagai donatur',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildStatCard(
title: 'Total Bantuan',
value: '${controller.penitipanBantuan.length}',
icon: Icons.card_giftcard,
color: Colors.blue,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatCard(
title: 'Menunggu Verifikasi',
value:
'${controller.penitipanBantuan.where((p) => p.status == 'MENUNGGU').length}',
icon: Icons.hourglass_empty,
color: Colors.orange,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatCard(
title: 'Diterima',
value:
'${controller.penitipanBantuan.where((p) => p.status == 'DITERIMA').length}',
icon: Icons.check_circle_outline,
color: Colors.green,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatCard(
title: 'Ditolak',
value:
'${controller.penitipanBantuan.where((p) => p.status == 'DITOLAK').length}',
icon: Icons.cancel_outlined,
color: Colors.red,
),
),
],
),
],
);
}
Widget _buildUpcomingEvents() {
final upcomingEvents = controller.jadwalPenyaluran
.where((event) =>
event.tanggalPenyaluran != null &&
event.tanggalPenyaluran!.isAfter(DateTime.now()))
.take(3)
.toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SectionHeader(
title: 'Jadwal Penyaluran',
),
Text(
'Jadwal penyaluran bantuan terdekat',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
],
),
TextButton(
onPressed: () {
// Navigasi ke tab jadwal penyaluran
controller.activeTabIndex.value = 2;
},
child: Text(
'Lihat Semua',
style: TextStyle(color: Colors.blue.shade700),
),
),
],
),
const SizedBox(height: 8),
if (upcomingEvents.isEmpty)
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(10),
),
child: const Center(
child: Text(
'Tidak ada jadwal penyaluran dalam waktu dekat',
style: TextStyle(color: Colors.grey),
),
),
)
else
...upcomingEvents.map((event) => _buildEventCard(event)),
],
);
}
Widget _buildRecentPenitipan() {
final recentPenitipan = controller.penitipanBantuan.take(3).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SectionHeader(
title: 'Bantuan Terakhir',
),
Text(
'Riwayat penitipan bantuan terakhir',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
],
),
TextButton(
onPressed: () {
// Navigasi ke tab riwayat penitipan
controller.activeTabIndex.value = 3;
},
child: Text(
'Lihat Semua',
style: TextStyle(color: Colors.blue.shade700),
),
),
],
),
const SizedBox(height: 8),
if (recentPenitipan.isEmpty)
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(10),
),
child: const Center(
child: Text(
'Belum ada riwayat penitipan bantuan',
style: TextStyle(color: Colors.grey),
),
),
)
else
...recentPenitipan.map((penitipan) => _buildPenitipanCard(penitipan)),
],
);
}
Widget _buildInfoRow({
required IconData icon,
required Color iconColor,
required String label,
required String value,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
size: 16,
color: iconColor,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
);
}
Widget _buildActionButton({
required IconData icon,
required String label,
required Color color,
required VoidCallback onTap,
}) {
return ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: color,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 16),
const SizedBox(width: 8),
Text(label),
],
),
);
}
Widget _buildStatCard({
required String title,
required String value,
required IconData icon,
required Color color,
}) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: color,
size: 24,
),
),
const SizedBox(height: 12),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
),
),
);
}
Widget _buildEventCard(dynamic event) {
final formattedDate = event.tanggalPenyaluran != null
? DateFormat('dd MMMM yyyy', 'id_ID').format(event.tanggalPenyaluran!)
: 'Tanggal tidak tersedia';
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.event,
color: Colors.blue.shade700,
size: 24,
),
Text(
event.tanggalPenyaluran != null
? DateFormat('dd').format(event.tanggalPenyaluran!)
: '--',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.blue.shade700,
),
),
],
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.nama ?? 'Penyaluran Bantuan',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
formattedDate,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 4),
Text(
event.deskripsi ?? 'Tidak ada deskripsi',
style: const TextStyle(fontSize: 14),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
),
);
}
Widget _buildPenitipanCard(dynamic penitipan) {
final formattedDate = penitipan.tanggalPenitipan != null
? DateFormat('dd MMMM yyyy', 'id_ID')
.format(penitipan.tanggalPenitipan!)
: 'Tanggal tidak tersedia';
Color statusColor;
IconData statusIcon;
switch (penitipan.status) {
case 'DITERIMA':
statusColor = Colors.green;
statusIcon = Icons.check_circle;
break;
case 'DITOLAK':
statusColor = Colors.red;
statusIcon = Icons.cancel;
break;
case 'MENUNGGU':
default:
statusColor = Colors.orange;
statusIcon = Icons.hourglass_empty;
break;
}
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
statusIcon,
color: statusColor,
size: 24,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
penitipan.kategoriBantuan?.nama ?? 'Bantuan',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
penitipan.status ?? 'MENUNGGU',
style: TextStyle(
fontSize: 12,
color: statusColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 4),
Text(
formattedDate,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.inventory_2_outlined,
size: 14,
color: Colors.grey.shade600,
),
const SizedBox(width: 4),
Text(
'Jumlah: ${penitipan.jumlah ?? 0}',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade800,
),
),
],
),
],
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,322 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
import 'package:penyaluran_app/app/widgets/section_header.dart';
class DonaturJadwalView extends GetView<DonaturDashboardController> {
const DonaturJadwalView({super.key});
@override
DonaturDashboardController get controller {
if (!Get.isRegistered<DonaturDashboardController>(
tag: 'donatur_dashboard')) {
return Get.put(DonaturDashboardController(),
tag: 'donatur_dashboard', permanent: true);
}
return Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return RefreshIndicator(
onRefresh: () async {
await controller.fetchJadwalPenyaluran();
},
child: controller.jadwalPenyaluran.isEmpty
? _buildEmptyState()
: _buildJadwalList(),
);
}),
);
}
Widget _buildEmptyState() {
return Center(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.event_busy,
size: 80,
color: Colors.grey.shade400,
),
const SizedBox(height: 16),
const Text(
'Belum Ada Jadwal Penyaluran',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Jadwal penyaluran bantuan belum tersedia saat ini',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () => controller.fetchJadwalPenyaluran(),
icon: const Icon(Icons.refresh),
label: const Text('Muat Ulang'),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue,
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
),
);
}
Widget _buildJadwalList() {
// Kelompokkan jadwal berdasarkan bulan
Map<String, List<dynamic>> groupedJadwal = {};
for (var jadwal in controller.jadwalPenyaluran) {
if (jadwal.tanggalPenyaluran != null) {
String monthYear =
DateFormat('MMMM yyyy', 'id_ID').format(jadwal.tanggalPenyaluran!);
if (!groupedJadwal.containsKey(monthYear)) {
groupedJadwal[monthYear] = [];
}
groupedJadwal[monthYear]!.add(jadwal);
}
}
// Urutkan kunci (bulan) secara kronologis
List<String> sortedMonths = groupedJadwal.keys.toList()
..sort((a, b) {
DateTime dateA = DateFormat('MMMM yyyy', 'id_ID').parse(a);
DateTime dateB = DateFormat('MMMM yyyy', 'id_ID').parse(b);
return dateA.compareTo(dateB);
});
return ListView(
padding: const EdgeInsets.all(16),
children: [
const SectionHeader(title: 'Jadwal Penyaluran Bantuan'),
Text(
'Daftar jadwal penyaluran bantuan yang akan datang',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 16),
// Tampilkan jadwal berdasarkan bulan
...sortedMonths
.map((month) => _buildMonthSection(month, groupedJadwal[month]!)),
],
);
}
Widget _buildMonthSection(String month, List<dynamic> jadwalList) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 8),
child: Text(
month,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
...jadwalList.map((jadwal) => _buildJadwalCard(jadwal)),
const SizedBox(height: 8),
],
);
}
Widget _buildJadwalCard(dynamic jadwal) {
final formattedDate = jadwal.tanggalPenyaluran != null
? DateFormat('EEEE, dd MMMM yyyy', 'id_ID')
.format(jadwal.tanggalPenyaluran!)
: 'Tanggal tidak tersedia';
String statusText = 'Akan Datang';
Color statusColor = Colors.blue;
switch (jadwal.status) {
case 'SELESAI':
statusText = 'Selesai';
statusColor = Colors.green;
break;
case 'DIBATALKAN':
statusText = 'Dibatalkan';
statusColor = Colors.red;
break;
case 'DALAM_PROSES':
statusText = 'Dalam Proses';
statusColor = Colors.orange;
break;
default:
statusText = 'Akan Datang';
statusColor = Colors.blue;
}
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (jadwal.tanggalPenyaluran != null) ...[
Text(
DateFormat('dd').format(jadwal.tanggalPenyaluran!),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: statusColor,
),
),
Text(
DateFormat('MMM', 'id_ID')
.format(jadwal.tanggalPenyaluran!)
.toUpperCase(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: statusColor,
),
),
] else
Icon(
Icons.event,
color: statusColor,
size: 24,
),
],
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
jadwal.nama ?? 'Penyaluran Bantuan',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.calendar_today,
size: 14,
color: Colors.grey.shade600,
),
const SizedBox(width: 4),
Text(
formattedDate,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
),
),
],
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
statusText,
style: TextStyle(
fontSize: 12,
color: statusColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
],
),
if (jadwal.deskripsi != null && jadwal.deskripsi!.isNotEmpty) ...[
const Divider(height: 24),
Text(
jadwal.deskripsi!,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
),
);
}
}

View File

@ -0,0 +1,409 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
import 'package:penyaluran_app/app/widgets/section_header.dart';
class DonaturLaporanView extends GetView<DonaturDashboardController> {
const DonaturLaporanView({super.key});
@override
DonaturDashboardController get controller {
if (!Get.isRegistered<DonaturDashboardController>(
tag: 'donatur_dashboard')) {
return Get.put(DonaturDashboardController(),
tag: 'donatur_dashboard', permanent: true);
}
return Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return RefreshIndicator(
onRefresh: () async {
await controller.fetchLaporanPenyaluran();
},
child: controller.laporanPenyaluran.isEmpty
? _buildEmptyState()
: _buildLaporanList(),
);
}),
);
}
Widget _buildEmptyState() {
return Center(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.assignment_outlined,
size: 80,
color: Colors.grey.shade400,
),
const SizedBox(height: 16),
const Text(
'Belum Ada Laporan Penyaluran',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Laporan penyaluran bantuan belum tersedia',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () => controller.fetchLaporanPenyaluran(),
icon: const Icon(Icons.refresh),
label: const Text('Muat Ulang'),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue,
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
),
);
}
Widget _buildLaporanList() {
// Urutkan laporan berdasarkan tanggal, yang terbaru di atas
final sortedLaporan = controller.laporanPenyaluran.toList()
..sort((a, b) {
if (a.tanggalLaporan == null || b.tanggalLaporan == null) {
return 0;
}
return b.tanggalLaporan!.compareTo(a.tanggalLaporan!);
});
return ListView(
padding: const EdgeInsets.all(16),
children: [
const SectionHeader(title: 'Laporan Penyaluran Bantuan'),
Text(
'Daftar laporan hasil penyaluran bantuan',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 16),
...sortedLaporan.map((laporan) => _buildLaporanCard(laporan)),
],
);
}
Widget _buildLaporanCard(dynamic laporan) {
final formattedDate = laporan.tanggalLaporan != null
? DateFormat('dd MMMM yyyy', 'id_ID').format(laporan.tanggalLaporan!)
: 'Tanggal tidak tersedia';
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 60,
height: 60,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(10),
),
child: laporan.dokumentasiUrl != null &&
laporan.dokumentasiUrl!.isNotEmpty
? ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
laporan.dokumentasiUrl!,
width: 60,
height: 60,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.assignment,
color: Colors.blue.shade700,
size: 30,
);
},
),
)
: Icon(
Icons.assignment,
color: Colors.blue.shade700,
size: 30,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
laporan.judul,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.calendar_today,
size: 14,
color: Colors.grey.shade600,
),
const SizedBox(width: 4),
Text(
formattedDate,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
if (laporan.beritaAcaraUrl != null &&
laporan.beritaAcaraUrl!.isNotEmpty)
ElevatedButton.icon(
onPressed: () {
// Implementasi untuk membuka berita acara
_openDocument(laporan.beritaAcaraUrl!);
},
icon: const Icon(Icons.description, size: 16),
label: const Text('Berita Acara'),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
minimumSize: const Size(30, 30),
tapTargetSize:
MaterialTapTargetSize.shrinkWrap,
textStyle: const TextStyle(fontSize: 12),
),
),
if (laporan.dokumentasiUrl != null &&
laporan.dokumentasiUrl!.isNotEmpty) ...[
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: () {
// Implementasi untuk melihat dokumentasi
_viewDocumentation(laporan.dokumentasiUrl!);
},
icon: const Icon(Icons.photo_library, size: 16),
label: const Text('Dokumentasi'),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.green,
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
minimumSize: const Size(30, 30),
tapTargetSize:
MaterialTapTargetSize.shrinkWrap,
textStyle: const TextStyle(fontSize: 12),
),
),
],
],
),
],
),
),
],
),
],
),
),
),
);
}
void _openDocument(String url) {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Buka Dokumen',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
const Text(
'Anda akan membuka dokumen berita acara. Lanjutkan?',
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => Get.back(),
child: const Text('Batal'),
),
ElevatedButton(
onPressed: () {
Get.back();
// Implementasi untuk membuka URL dokumen
// Misalnya menggunakan package url_launcher
// launch(url);
Get.snackbar(
'Info',
'Membuka dokumen: $url',
snackPosition: SnackPosition.BOTTOM,
);
},
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue,
),
child: const Text('Buka'),
),
],
),
],
),
),
),
);
}
void _viewDocumentation(String url) {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
insetPadding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Dokumentasi',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Get.back(),
),
],
),
),
Container(
constraints: BoxConstraints(
maxHeight: Get.height * 0.6,
maxWidth: Get.width,
),
child: Image.network(
url,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 50,
color: Colors.red.shade300,
),
const SizedBox(height: 16),
const Text('Gagal memuat gambar'),
],
),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
),
const SizedBox(height: 16),
],
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,393 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
class DonaturRiwayatPenitipanView extends GetView<DonaturDashboardController> {
DonaturRiwayatPenitipanView({Key? key}) : super(key: key);
@override
DonaturDashboardController get controller {
return Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
}
final TextEditingController searchController = TextEditingController();
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: const Text('Riwayat Penitipan'),
bottom: const TabBar(
tabs: [
Tab(text: 'Menunggu'),
Tab(text: 'Diterima'),
Tab(text: 'Ditolak'),
],
),
),
body: TabBarView(
children: [
// Tab Menunggu
_buildPenitipanList(context, 'MENUNGGU'),
// Tab Diterima
_buildPenitipanList(context, 'DITERIMA'),
// Tab Ditolak
_buildPenitipanList(context, 'DITOLAK'),
],
),
),
);
}
Widget _buildPenitipanList(BuildContext context, String status) {
return Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
// Filter penitipan berdasarkan status
var filteredList = controller.penitipanBantuan
.where((item) => item.status == status)
.toList();
// Filter berdasarkan pencarian
final searchText = searchController.text.toLowerCase();
if (searchText.isNotEmpty) {
filteredList = filteredList.where((item) {
final kategoriNama = item.kategoriBantuan?.nama?.toLowerCase() ?? '';
final deskripsi = item.deskripsi?.toLowerCase() ?? '';
final tanggal = item.tanggalPenitipan != null
? DateFormat('dd MMMM yyyy', 'id_ID')
.format(item.tanggalPenitipan!)
.toLowerCase()
: '';
return kategoriNama.contains(searchText) ||
deskripsi.contains(searchText) ||
tanggal.contains(searchText);
}).toList();
}
return RefreshIndicator(
onRefresh: () async {
await controller.fetchPenitipanBantuan();
},
child: filteredList.isEmpty
? _buildEmptyState(status)
: _buildContentList(context, filteredList, status),
);
});
}
Widget _buildEmptyState(String status) {
String statusText = '';
switch (status) {
case 'MENUNGGU':
statusText = 'menunggu verifikasi';
break;
case 'DITERIMA':
statusText = 'diterima';
break;
case 'DITOLAK':
statusText = 'ditolak';
break;
}
return Center(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.inventory_2_outlined,
size: 80,
color: Colors.grey.shade400,
),
const SizedBox(height: 16),
Text(
'Tidak ada penitipan $statusText',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Anda belum memiliki riwayat penitipan yang $statusText',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
),
],
),
),
);
}
Widget _buildContentList(
BuildContext context, List<dynamic> filteredList, String status) {
Color statusColor;
switch (status) {
case 'DITERIMA':
statusColor = Colors.green;
break;
case 'DITOLAK':
statusColor = Colors.red;
break;
case 'MENUNGGU':
default:
statusColor = Colors.orange;
break;
}
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Search field
TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'Cari riwayat penitipan...',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey.shade100,
contentPadding: const EdgeInsets.symmetric(vertical: 0),
),
onChanged: (value) {
// Trigger update dengan GetX
controller.update();
},
),
const SizedBox(height: 16),
// Info jumlah item
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Daftar Penitipan ${status.toLowerCase()}',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
'${filteredList.length} item',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey,
),
),
],
),
const SizedBox(height: 16),
// Daftar penitipan
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: filteredList.length,
itemBuilder: (context, index) {
return _buildPenitipanCard(
context, filteredList[index], statusColor);
},
),
],
),
),
);
}
Widget _buildPenitipanCard(
BuildContext context, dynamic penitipan, Color statusColor) {
final formattedDate = penitipan.tanggalPenitipan != null
? DateFormat('dd MMMM yyyy', 'id_ID')
.format(penitipan.tanggalPenitipan!)
: 'Tanggal tidak tersedia';
IconData statusIcon;
switch (penitipan.status) {
case 'DITERIMA':
statusIcon = Icons.check_circle;
break;
case 'DITOLAK':
statusIcon = Icons.cancel;
break;
case 'MENUNGGU':
default:
statusIcon = Icons.hourglass_empty;
break;
}
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
statusIcon,
color: statusColor,
size: 24,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
penitipan.kategoriBantuan?.nama ?? 'Bantuan',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
penitipan.status ?? 'MENUNGGU',
style: TextStyle(
fontSize: 12,
color: statusColor,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.calendar_today,
size: 14,
color: Colors.grey.shade600,
),
const SizedBox(width: 4),
Text(
formattedDate,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.inventory_2_outlined,
size: 14,
color: Colors.grey.shade600,
),
const SizedBox(width: 4),
Text(
'Jumlah: ${penitipan.jumlah ?? 0}',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade800,
),
),
],
),
],
),
),
],
),
if (penitipan.deskripsi != null &&
penitipan.deskripsi!.isNotEmpty) ...[
const Divider(height: 24),
Text(
penitipan.deskripsi!,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
if (penitipan.status == 'DITOLAK' &&
penitipan.alasanPenolakan != null &&
penitipan.alasanPenolakan!.isNotEmpty) ...[
const Divider(height: 24),
Text(
'Alasan Penolakan:',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.red.shade700,
),
),
const SizedBox(height: 4),
Text(
penitipan.alasanPenolakan!,
style: TextStyle(
fontSize: 14,
color: Colors.red.shade700,
),
),
],
],
),
),
),
);
}
}

View File

@ -0,0 +1,274 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
import 'package:penyaluran_app/app/widgets/section_header.dart';
class DonaturSkemaView extends GetView<DonaturDashboardController> {
const DonaturSkemaView({super.key});
@override
DonaturDashboardController get controller {
if (!Get.isRegistered<DonaturDashboardController>(
tag: 'donatur_dashboard')) {
return Get.put(DonaturDashboardController(),
tag: 'donatur_dashboard', permanent: true);
}
return Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return RefreshIndicator(
onRefresh: () async {
await controller.fetchSkemaBantuan();
},
child: controller.skemaBantuan.isEmpty
? _buildEmptyState()
: _buildSkemaList(),
);
}),
);
}
Widget _buildEmptyState() {
return Center(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.description_outlined,
size: 80,
color: Colors.grey.shade400,
),
const SizedBox(height: 16),
const Text(
'Belum Ada Skema Bantuan',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Skema bantuan belum tersedia saat ini',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () => controller.fetchSkemaBantuan(),
icon: const Icon(Icons.refresh),
label: const Text('Muat Ulang'),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue,
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
),
);
}
Widget _buildSkemaList() {
return ListView(
padding: const EdgeInsets.all(16),
children: [
const SectionHeader(title: 'Skema Bantuan Tersedia'),
Text(
'Daftar skema bantuan yang dapat Anda titipkan',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 16),
...controller.skemaBantuan.map((skema) => _buildSkemaCard(skema)),
],
);
}
Widget _buildSkemaCard(dynamic skema) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.08),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.volunteer_activism,
color: Colors.blue.shade700,
size: 24,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
skema.nama ?? 'Skema Bantuan',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
if (skema.kuota != null)
_buildInfoChip(
icon: Icons.people_outline,
label: 'Kuota: ${skema.kuota} penerima',
color: Colors.blue.shade700,
),
if (skema.jumlahDiterimaPerOrang != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: _buildInfoChip(
icon: Icons.inventory_2_outlined,
label:
'Jumlah per orang: ${skema.jumlahDiterimaPerOrang}',
color: Colors.green.shade700,
),
),
],
),
),
],
),
const SizedBox(height: 16),
if (skema.deskripsi != null && skema.deskripsi!.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Deskripsi',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
const SizedBox(height: 4),
Text(
skema.deskripsi!,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
),
const Divider(height: 24),
],
),
if (skema.kriteria != null && skema.kriteria!.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Kriteria Penerima',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
const SizedBox(height: 4),
Text(
skema.kriteria!,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
),
const SizedBox(height: 16),
],
),
ElevatedButton.icon(
onPressed: () {
// Navigasi ke formulir penitipan bantuan
controller.activeTabIndex.value = 3;
},
icon: const Icon(Icons.add_box_outlined, size: 18),
label: const Text('Titipkan Bantuan'),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.green.shade600,
minimumSize: const Size(double.infinity, 45),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
),
),
);
}
Widget _buildInfoChip({
required IconData icon,
required String label,
required Color color,
}) {
return Row(
children: [
Icon(
icon,
size: 14,
color: color,
),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 13,
color: color,
fontWeight: FontWeight.w500,
),
),
],
);
}
}

View File

@ -0,0 +1,326 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
import 'package:penyaluran_app/app/modules/donatur/views/donatur_dashboard_view.dart';
import 'package:penyaluran_app/app/modules/donatur/views/donatur_skema_view.dart';
import 'package:penyaluran_app/app/modules/donatur/views/donatur_jadwal_view.dart';
import 'package:penyaluran_app/app/modules/donatur/views/donatur_laporan_view.dart';
import 'package:penyaluran_app/app/modules/donatur/views/donatur_penitipan_view.dart';
import 'package:penyaluran_app/app/widgets/app_bottom_navigation_bar.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart';
class DonaturView extends GetView<DonaturDashboardController> {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
DonaturView({super.key});
// Override untuk mendapatkan controller dengan tag
@override
DonaturDashboardController get controller {
if (!Get.isRegistered<DonaturDashboardController>(
tag: 'donatur_dashboard')) {
return Get.put(DonaturDashboardController(),
tag: 'donatur_dashboard', permanent: true);
}
return Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Obx(() {
switch (controller.activeTabIndex.value) {
case 0:
return const Text('Dashboard Donatur');
case 1:
return const Text('Skema Bantuan');
case 2:
return const Text('Jadwal Penyaluran');
case 3:
return const Text('Penitipan Bantuan');
case 4:
return const Text('Laporan Penyaluran');
default:
return const Text('Dashboard Donatur');
}
}),
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {
scaffoldKey.currentState?.openDrawer();
},
),
actions: [
// Tombol riwayat penitipan khusus untuk tab penitipan bantuan
Obx(() => controller.activeTabIndex.value == 3
? IconButton(
icon: const Icon(Icons.history),
onPressed: () {
// Navigasi ke halaman riwayat penitipan
Get.to(
() => DonaturRiwayatPenitipanView(),
transition: Transition.rightToLeft,
);
},
)
: const SizedBox.shrink()),
// 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: _buildDrawer(context),
body: Obx(() {
// Tampilkan sesuai dengan tab yang aktif
switch (controller.activeTabIndex.value) {
case 0:
return const DonaturDashboardView();
case 1:
return const DonaturSkemaView();
case 2:
return const DonaturJadwalView();
case 3:
return const DonaturPenitipanView();
case 4:
return const DonaturLaporanView();
default:
return const DonaturDashboardView();
}
}),
bottomNavigationBar: Obx(() => AppBottomNavigationBar(
currentIndex: controller.activeTabIndex.value,
onTap: (index) {
controller.activeTabIndex.value = index;
},
items: [
AppBottomNavigationBarItem(
icon: Icons.dashboard_outlined,
activeIcon: Icons.dashboard,
label: 'Dashboard',
),
AppBottomNavigationBarItem(
icon: Icons.description_outlined,
activeIcon: Icons.description,
label: 'Skema',
),
AppBottomNavigationBarItem(
icon: Icons.calendar_today_outlined,
activeIcon: Icons.calendar_today,
label: 'Jadwal',
),
AppBottomNavigationBarItem(
icon: Icons.add_box_outlined,
activeIcon: Icons.add_box,
label: 'Penitipan',
),
AppBottomNavigationBarItem(
icon: Icons.assignment_outlined,
activeIcon: Icons.assignment,
label: 'Laporan',
),
],
)),
);
}
Widget _buildDrawer(BuildContext context) {
return Drawer(
child: Column(
children: [
Container(
decoration: BoxDecoration(
gradient: AppTheme.primaryGradient,
),
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 16,
bottom: 24,
left: 16,
right: 16),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: CircleAvatar(
radius: 40,
backgroundColor: Colors.white70,
backgroundImage: controller.profilePhotoUrl != null &&
controller.profilePhotoUrl!.isNotEmpty
? NetworkImage(controller.profilePhotoUrl!)
: null,
child: controller.profilePhotoUrl == null ||
controller.profilePhotoUrl!.isEmpty
? const Icon(
Icons.person,
color: Colors.white,
size: 40,
)
: null,
),
),
const SizedBox(height: 16),
const Text(
'Halo,',
style: TextStyle(
color: Colors.white70,
fontSize: 16,
),
),
Text(
controller.nama,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 22,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
const SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'Donatur',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.location_on,
color: Colors.white,
size: 14,
),
const SizedBox(width: 4),
Text(
controller.desa ?? 'Tidak ada desa',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
],
),
),
],
),
],
),
),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
leading: const Icon(Icons.person_outline),
title: const Text('Profil'),
onTap: () {
Navigator.pop(context);
Get.toNamed('/profile');
},
),
ListTile(
leading: const Icon(Icons.history),
title: const Text('Riwayat Donasi'),
onTap: () {
Navigator.pop(context);
// TODO: Implementasi riwayat donasi
},
),
ListTile(
leading: const Icon(Icons.settings_outlined),
title: const Text('Pengaturan'),
onTap: () {
Navigator.pop(context);
// TODO: Implementasi pengaturan
},
),
const Divider(),
ListTile(
leading: const Icon(Icons.logout),
title: const Text('Keluar'),
onTap: () {
Navigator.pop(context);
controller.logout();
},
),
],
),
),
],
),
);
}
}

View File

@ -36,6 +36,8 @@ import 'package:penyaluran_app/app/modules/laporan_penyaluran/views/laporan_peny
import 'package:penyaluran_app/app/modules/laporan_penyaluran/views/laporan_penyaluran_create_view.dart';
import 'package:penyaluran_app/app/modules/laporan_penyaluran/views/laporan_penyaluran_edit_view.dart';
import 'package:penyaluran_app/app/modules/laporan_penyaluran/bindings/laporan_penyaluran_binding.dart';
import 'package:penyaluran_app/app/modules/donatur/views/donatur_view.dart';
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
part 'app_routes.dart';
@ -159,17 +161,17 @@ class AppPages {
binding: LaporanPenyaluranBinding(),
),
GetPage(
name: _Paths.laporanPenyaluran + '/detail',
name: '${_Paths.laporanPenyaluran}/detail',
page: () => const LaporanPenyaluranDetailView(),
binding: LaporanPenyaluranBinding(),
),
GetPage(
name: _Paths.laporanPenyaluran + '/create',
name: '${_Paths.laporanPenyaluran}/create',
page: () => const LaporanPenyaluranCreateView(),
binding: LaporanPenyaluranBinding(),
),
GetPage(
name: _Paths.laporanPenyaluran + '/edit',
name: '${_Paths.laporanPenyaluran}/edit',
page: () => const LaporanPenyaluranEditView(),
binding: LaporanPenyaluranBinding(),
),
@ -186,5 +188,50 @@ class AppPages {
),
binding: PenyaluranBinding(),
),
GetPage(
name: Routes.donaturDashboard,
page: () => DonaturView(),
binding: DonaturBinding(),
),
GetPage(
name: _Paths.donaturSkema,
page: () {
final controller =
Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
controller.activeTabIndex.value = 1;
return DonaturView();
},
binding: DonaturBinding(),
),
GetPage(
name: _Paths.donaturJadwal,
page: () {
final controller =
Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
controller.activeTabIndex.value = 2;
return DonaturView();
},
binding: DonaturBinding(),
),
GetPage(
name: _Paths.donaturPenitipan,
page: () {
final controller =
Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
controller.activeTabIndex.value = 3;
return DonaturView();
},
binding: DonaturBinding(),
),
GetPage(
name: _Paths.donaturLaporan,
page: () {
final controller =
Get.find<DonaturDashboardController>(tag: 'donatur_dashboard');
controller.activeTabIndex.value = 4;
return DonaturView();
},
binding: DonaturBinding(),
),
];
}

View File

@ -11,6 +11,10 @@ abstract class Routes {
static const petugasVerifikasiDashboard = _Paths.petugasVerifikasiDashboard;
static const petugasDesaDashboard = _Paths.petugasDesaDashboard;
static const donaturDashboard = _Paths.donaturDashboard;
static const donaturSkema = _Paths.donaturSkema;
static const donaturJadwal = _Paths.donaturJadwal;
static const donaturPenitipan = _Paths.donaturPenitipan;
static const donaturLaporan = _Paths.donaturLaporan;
static const splash = _Paths.splash;
static const permintaanPenjadwalan = _Paths.permintaanPenjadwalan;
static const daftarPenerima = _Paths.daftarPenerima;
@ -51,6 +55,10 @@ abstract class _Paths {
static const petugasVerifikasiDashboard = '/petugas-verifikasi-dashboard';
static const petugasDesaDashboard = '/petugas-desa-dashboard';
static const donaturDashboard = '/donatur-dashboard';
static const donaturSkema = '/donatur-skema';
static const donaturJadwal = '/donatur-jadwal';
static const donaturPenitipan = '/donatur-penitipan';
static const donaturLaporan = '/donatur-laporan';
static const splash = '/splash';
static const permintaanPenjadwalan = '/permintaan-penjadwalan';
static const daftarPenerima = '/daftar-penerima';