Perbarui dependensi dengan menambahkan paket percent_indicator versi 4.2.4. Modifikasi file pubspec.yaml dan pubspec.lock untuk mencerminkan perubahan ini. Selain itu, perbarui status penerimaan di PelaksanaanPenyaluranController dari 'SUDAHMENERIMA' menjadi 'DITERIMA' untuk konsistensi. Tambahkan fungsionalitas baru di PetugasDesaDashboardController untuk memuat jadwal hari ini dan total penitipan terverifikasi. Perbarui tampilan di beberapa view untuk meningkatkan pengalaman pengguna dan konsistensi data.
This commit is contained in:
@ -2,27 +2,27 @@ C/C++ Structured LogO
|
|||||||
M
|
M
|
||||||
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
||||||
A
|
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>
|
<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
|
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
|
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
|
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
|
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 ~
|
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
|
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>
|
||||||
<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
|
<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
|
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
|
M
|
||||||
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
||||||
A
|
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>
|
<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>
|
||||||
<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
|
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
|
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
|
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|
|
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>
|
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
|
|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>
|
||||||
<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
|
<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
|
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
|
M
|
||||||
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
||||||
A
|
A
|
||||||
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint <EFBFBD><EFBFBD><EFBFBD><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{
|
||||||
y
|
y
|
||||||
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt <EFBFBD><EFBFBD><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 ؔ<EFBFBD><EFBFBD><EFBFBD>2 <20><><EFBFBD><EFBFBD><EFBFBD>2x
|
||||||
v
|
v
|
||||||
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json <EFBFBD><EFBFBD><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 ؔ<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 <EFBFBD><EFBFBD><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 ؔ<EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j
|
||||||
h
|
h
|
||||||
fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja <EFBFBD><EFBFBD><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 ؔ<EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n
|
||||||
l
|
l
|
||||||
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2s
|
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt ؔ<EFBFBD><EFBFBD><EFBFBD>2s
|
||||||
q
|
q
|
||||||
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2
|
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt ؔ<EFBFBD><EFBFBD><EFBFBD>2
|
||||||
K <20><><EFBFBD><EFBFBD><EFBFBD>2t
|
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 ؔ<><D894><EFBFBD>2 x
|
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json ؔ<><D894><EFBFBD>2 x
|
||||||
v
|
v
|
||||||
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin ؔ<><D894><EFBFBD>2
|
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin ؔ<><D894><EFBFBD>2
|
||||||
~
|
~
|
||||||
|
|
|
|
||||||
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt ؔ<><D894><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2q
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt ؔ<><D894><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 ؔ<><D894><EFBFBD>2
|
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json ؔ<><D894><EFBFBD>2
|
||||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2v
|
( <20><><EFBFBD><EFBFBD><EFBFBD>2v
|
@ -2,27 +2,27 @@ C/C++ Structured LogO
|
|||||||
M
|
M
|
||||||
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
|
||||||
A
|
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
|
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
|
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
|
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
|
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
|
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 {
|
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
|
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>
|
<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
|
}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
|
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
|
@ -287,7 +287,7 @@ class PelaksanaanPenyaluranController extends GetxController {
|
|||||||
// Metode untuk mendapatkan warna status penerimaan
|
// Metode untuk mendapatkan warna status penerimaan
|
||||||
Color getStatusColor(String status) {
|
Color getStatusColor(String status) {
|
||||||
switch (status.toUpperCase()) {
|
switch (status.toUpperCase()) {
|
||||||
case 'SUDAHMENERIMA':
|
case 'DITERIMA':
|
||||||
return AppTheme.successColor;
|
return AppTheme.successColor;
|
||||||
case 'BELUMMENERIMA':
|
case 'BELUMMENERIMA':
|
||||||
return AppTheme.warningColor;
|
return AppTheme.warningColor;
|
||||||
@ -299,7 +299,7 @@ class PelaksanaanPenyaluranController extends GetxController {
|
|||||||
// Metode untuk mendapatkan ikon status penerimaan
|
// Metode untuk mendapatkan ikon status penerimaan
|
||||||
IconData getStatusIcon(String status) {
|
IconData getStatusIcon(String status) {
|
||||||
switch (status.toUpperCase()) {
|
switch (status.toUpperCase()) {
|
||||||
case 'SUDAHMENERIMA':
|
case 'DITERIMA':
|
||||||
return Icons.check_circle;
|
return Icons.check_circle;
|
||||||
case 'BELUMMENERIMA':
|
case 'BELUMMENERIMA':
|
||||||
return Icons.event_available;
|
return Icons.event_available;
|
||||||
@ -311,7 +311,7 @@ class PelaksanaanPenyaluranController extends GetxController {
|
|||||||
// Metode untuk mendapatkan teks status penerimaan
|
// Metode untuk mendapatkan teks status penerimaan
|
||||||
String getStatusText(String status) {
|
String getStatusText(String status) {
|
||||||
switch (status.toUpperCase()) {
|
switch (status.toUpperCase()) {
|
||||||
case 'SUDAHMENERIMA':
|
case 'DITERIMA':
|
||||||
return 'Sudah Menerima';
|
return 'Sudah Menerima';
|
||||||
case 'BELUMMENERIMA':
|
case 'BELUMMENERIMA':
|
||||||
return 'Belum Menerima';
|
return 'Belum Menerima';
|
||||||
@ -373,7 +373,7 @@ class PelaksanaanPenyaluranController extends GetxController {
|
|||||||
createdAt: penerimaPenyaluran[index].createdAt,
|
createdAt: penerimaPenyaluran[index].createdAt,
|
||||||
penyaluranBantuanId: penerimaPenyaluran[index].penyaluranBantuanId,
|
penyaluranBantuanId: penerimaPenyaluran[index].penyaluranBantuanId,
|
||||||
wargaId: penerimaPenyaluran[index].wargaId,
|
wargaId: penerimaPenyaluran[index].wargaId,
|
||||||
statusPenerimaan: 'SUDAHMENERIMA',
|
statusPenerimaan: 'DITERIMA',
|
||||||
tanggalPenerimaan: penerimaPenyaluran[index].tanggalPenerimaan,
|
tanggalPenerimaan: penerimaPenyaluran[index].tanggalPenerimaan,
|
||||||
buktiPenerimaan: penerimaPenyaluran[index].buktiPenerimaan,
|
buktiPenerimaan: penerimaPenyaluran[index].buktiPenerimaan,
|
||||||
keterangan: penerimaPenyaluran[index].keterangan,
|
keterangan: penerimaPenyaluran[index].keterangan,
|
||||||
|
@ -18,12 +18,18 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
final RxInt totalPenerima = 0.obs;
|
final RxInt totalPenerima = 0.obs;
|
||||||
final RxInt totalBantuan = 0.obs;
|
final RxInt totalBantuan = 0.obs;
|
||||||
final RxInt totalPenyaluran = 0.obs;
|
final RxInt totalPenyaluran = 0.obs;
|
||||||
|
final RxInt totalSemuaPenyaluran = 0.obs;
|
||||||
|
final RxInt totalPenitipanTerverifikasi = 0.obs;
|
||||||
final RxDouble progressPenyaluran = 0.0.obs;
|
final RxDouble progressPenyaluran = 0.0.obs;
|
||||||
|
|
||||||
// Data untuk notifikasi
|
// Data untuk notifikasi
|
||||||
final RxList<NotifikasiModel> notifikasiBelumDibaca = <NotifikasiModel>[].obs;
|
final RxList<NotifikasiModel> notifikasiBelumDibaca = <NotifikasiModel>[].obs;
|
||||||
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk jadwal hari ini
|
||||||
|
final RxList<Map<String, dynamic>> jadwalHariIni =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
|
||||||
// Controller untuk pencarian
|
// Controller untuk pencarian
|
||||||
final TextEditingController searchController = TextEditingController();
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
@ -45,6 +51,7 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
loadUserProfile();
|
loadUserProfile();
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
loadNotifikasiData();
|
loadNotifikasiData();
|
||||||
|
loadJadwalHariIni();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -76,18 +83,24 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
final penerimaData = await _supabaseService.getTotalPenerima();
|
final penerimaData = await _supabaseService.getTotalPenerima();
|
||||||
totalPenerima.value = penerimaData ?? 0;
|
totalPenerima.value = penerimaData ?? 0;
|
||||||
|
|
||||||
// Mengambil data total bantuan
|
// Mengambil data total penitipan terverifikasi
|
||||||
final bantuanData = await _supabaseService.getTotalBantuan();
|
final penitipanData =
|
||||||
totalBantuan.value = bantuanData ?? 0;
|
await _supabaseService.getTotalPenitipanTerverifikasi();
|
||||||
|
totalPenitipanTerverifikasi.value = penitipanData ?? 0;
|
||||||
|
|
||||||
// Mengambil data total penyaluran
|
// Mengambil data total penyaluran terlaksana
|
||||||
final penyaluranData = await _supabaseService.getTotalPenyaluran();
|
final penyaluranData = await _supabaseService.getTotalPenyaluran();
|
||||||
totalPenyaluran.value = penyaluranData ?? 0;
|
totalPenyaluran.value = penyaluranData ?? 0;
|
||||||
|
|
||||||
// Menghitung progress penyaluran
|
// Mengambil data total semua penyaluran
|
||||||
if (totalBantuan.value > 0) {
|
final semuaPenyaluranData =
|
||||||
|
await _supabaseService.getTotalSemuaPenyaluran();
|
||||||
|
totalSemuaPenyaluran.value = semuaPenyaluranData ?? 0;
|
||||||
|
|
||||||
|
// Menghitung progress penyaluran (persentase penyaluran yang terlaksana dari total semua penyaluran)
|
||||||
|
if (totalSemuaPenyaluran.value > 0) {
|
||||||
progressPenyaluran.value =
|
progressPenyaluran.value =
|
||||||
(totalPenyaluran.value / totalBantuan.value) * 100;
|
(totalPenyaluran.value / totalSemuaPenyaluran.value) * 100;
|
||||||
} else {
|
} else {
|
||||||
progressPenyaluran.value = 0.0;
|
progressPenyaluran.value = 0.0;
|
||||||
}
|
}
|
||||||
@ -114,11 +127,28 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> loadJadwalHariIni() async {
|
||||||
|
try {
|
||||||
|
final jadwalData = await _supabaseService.getJadwalHariIni();
|
||||||
|
if (jadwalData != null) {
|
||||||
|
jadwalHariIni.value = jadwalData;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading jadwal hari ini: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> refreshData() async {
|
Future<void> refreshData() async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await loadDashboardData();
|
await Future.wait([
|
||||||
await loadNotifikasiData();
|
loadUserProfile(),
|
||||||
|
loadDashboardData(),
|
||||||
|
loadNotifikasiData(),
|
||||||
|
loadJadwalHariIni(),
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error refreshing data: $e');
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -1,101 +1,301 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||||
|
import 'package:percent_indicator/circular_percent_indicator.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/greeting_header.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/greeting_header.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/progress_section.dart';
|
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/schedule_card.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/schedule_card.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
import 'package:penyaluran_app/app/widgets/cards/statistic_card.dart';
|
import 'package:penyaluran_app/app/widgets/cards/statistic_card.dart';
|
||||||
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
|
|
||||||
class DashboardView extends GetView<PetugasDesaController> {
|
class DashboardView extends GetView<PetugasDesaDashboardController> {
|
||||||
const DashboardView({super.key});
|
const DashboardView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return RefreshIndicator(
|
||||||
|
onRefresh: () => controller.refreshData(),
|
||||||
|
child: Obx(() => AnimationLimiter(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: controller.isLoading.value
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: AnimationConfiguration.staggeredList(
|
||||||
|
position: 0,
|
||||||
|
delay: const Duration(milliseconds: 100),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Header dengan greeting
|
// Header dengan greeting
|
||||||
GreetingHeader(
|
FadeInAnimation(
|
||||||
|
child: GreetingHeader(
|
||||||
name: controller.namaLengkap,
|
name: controller.namaLengkap,
|
||||||
role: 'Petugas Desa',
|
role: 'Petugas Desa',
|
||||||
desa: controller.desa,
|
desa: controller.desa,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Jadwal penyaluran hari ini
|
// Jadwal penyaluran hari ini
|
||||||
ScheduleCard(
|
FadeInAnimation(
|
||||||
title: 'Jadwal Penyaluran Hari ini',
|
delay: const Duration(milliseconds: 300),
|
||||||
location: 'Kantor Kepala Desa (Beras)',
|
child: _buildJadwalHariIni(),
|
||||||
dateTime: '15 April 2023, 13:00 - 14:00',
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Progress penyaluran
|
||||||
|
FadeInAnimation(
|
||||||
|
delay: const Duration(milliseconds: 400),
|
||||||
|
child: _buildProgressPenyaluran(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Statistik performa desa
|
||||||
|
FadeInAnimation(
|
||||||
|
delay: const Duration(milliseconds: 500),
|
||||||
|
child: _buildStatistikPerforma(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Daftar penerima terbaru
|
||||||
|
FadeInAnimation(
|
||||||
|
delay: const Duration(milliseconds: 600),
|
||||||
|
child: _buildRecipientsList(textTheme),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildJadwalHariIni() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Jadwal Penyaluran',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
FutureBuilder<List<Map<String, dynamic>>?>(
|
||||||
|
future: SupabaseService.to.getJadwalHariIni(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return const Center(child: Text('Gagal memuat jadwal'));
|
||||||
|
}
|
||||||
|
|
||||||
|
final jadwalList = snapshot.data;
|
||||||
|
|
||||||
|
if (jadwalList == null || jadwalList.isEmpty) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.event_busy, color: Colors.grey),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Tidak ada jadwal penyaluran hari ini'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: jadwalList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final jadwal = jadwalList[index];
|
||||||
|
final DateTime tanggal =
|
||||||
|
DateTime.parse(jadwal['tanggal_penyaluran']);
|
||||||
|
final String formattedDate =
|
||||||
|
DateTimeHelper.formatDateTime(tanggal);
|
||||||
|
final kategoriBantuan =
|
||||||
|
jadwal['kategori_bantuan'] as Map<String, dynamic>;
|
||||||
|
final lokasiPenyaluran =
|
||||||
|
jadwal['lokasi_penyaluran'] as Map<String, dynamic>;
|
||||||
|
|
||||||
|
return ScheduleCard(
|
||||||
|
title: kategoriBantuan['nama'] ?? 'Jadwal Penyaluran',
|
||||||
|
location: lokasiPenyaluran['nama'] ?? 'Lokasi tidak tersedia',
|
||||||
|
dateTime: formattedDate,
|
||||||
isToday: true,
|
isToday: true,
|
||||||
onTap: () => Get.toNamed('/petugas-desa/jadwal'),
|
onTap: () => Get.toNamed(Routes.detailPenyaluran,
|
||||||
|
parameters: {'id': jadwal['id']}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProgressPenyaluran() {
|
||||||
|
// Menghitung nilai untuk progress
|
||||||
|
final terlaksana = controller.totalPenyaluran.value;
|
||||||
|
final total = controller.totalSemuaPenyaluran.value;
|
||||||
|
final progressValue = total > 0 ? terlaksana / total : 0.0;
|
||||||
|
final belumTerlaksana = total - terlaksana;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppTheme.primaryGradient,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Progress Penyaluran',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
// Jadwal penyaluran mendatang
|
children: [
|
||||||
ScheduleCard(
|
CircularPercentIndicator(
|
||||||
title: 'Jadwal Penyaluran Mendatang',
|
radius: 60.0,
|
||||||
location: 'Balai Desa A (Sembako)',
|
lineWidth: 10.0,
|
||||||
dateTime: '17 April 2023, 13:00 - 14:00',
|
percent: progressValue > 1.0 ? 1.0 : progressValue,
|
||||||
isToday: false,
|
center: Text(
|
||||||
onTap: () => Get.toNamed('/petugas-desa/jadwal'),
|
'${(progressValue * 100).toInt()}%',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
|
progressColor: Colors.white,
|
||||||
|
backgroundColor: Colors.white.withOpacity(0.2),
|
||||||
|
circularStrokeCap: CircularStrokeCap.round,
|
||||||
|
animation: true,
|
||||||
|
animationDuration: 1200,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildProgressDetailItem(
|
||||||
|
'Telah Terlaksana',
|
||||||
|
'$terlaksana',
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildProgressDetailItem(
|
||||||
|
'Belum Terlaksana',
|
||||||
|
'$belumTerlaksana',
|
||||||
|
Colors.white.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildProgressDetailItem(
|
||||||
|
'Total Penyaluran',
|
||||||
|
'$total',
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Statistik penyaluran
|
Widget _buildProgressDetailItem(String label, String value, Color color) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatistikPerforma() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Statistik Performa',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: StatisticCard(
|
child: StatisticCard(
|
||||||
title: 'Penitipan',
|
title: 'Penitipan',
|
||||||
count: '3',
|
count: controller.jumlahNotifikasiBelumDibaca.toString(),
|
||||||
subtitle: 'Perlu Konfirmasi',
|
|
||||||
height: 120,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: StatisticCard(
|
|
||||||
title: 'Penjadwalan',
|
|
||||||
count: '1',
|
|
||||||
subtitle: 'Perlu Konfirmasi',
|
subtitle: 'Perlu Konfirmasi',
|
||||||
height: 120,
|
height: 120,
|
||||||
|
icon: Icons.inbox,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: StatisticCard(
|
child: StatisticCard(
|
||||||
title: 'Pengaduan',
|
title: 'Pengaduan',
|
||||||
count: '1',
|
count:
|
||||||
|
'${controller.totalPenerima.value > 0 ? controller.totalPenerima.value ~/ 10 : 0}',
|
||||||
subtitle: 'Perlu Tindakan',
|
subtitle: 'Perlu Tindakan',
|
||||||
height: 120,
|
height: 120,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.orange, Colors.deepOrange],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
icon: Icons.warning_amber,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Progress penyaluran
|
|
||||||
ProgressSection(
|
|
||||||
progressValue: 0.7,
|
|
||||||
total: 100,
|
|
||||||
distributed: 70,
|
|
||||||
scheduled: 20,
|
|
||||||
unscheduled: 10,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Daftar penerima
|
|
||||||
_buildRecipientsList(textTheme),
|
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,15 +307,16 @@ class DashboardView extends GetView<PetugasDesaController> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Daftar Penerima',
|
'Daftar Penerima Terbaru',
|
||||||
style: textTheme.titleMedium?.copyWith(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.toNamed('/daftar-penerima');
|
Get.toNamed(Routes.daftarPenerima);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -136,53 +337,107 @@ class DashboardView extends GetView<PetugasDesaController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
_buildRecipientItem(
|
FutureBuilder<List<Map<String, dynamic>>?>(
|
||||||
'Siti Rahayu', '3201020107030011', 'Selesai', textTheme),
|
future: SupabaseService.to.getPenerimaTerbaru(),
|
||||||
_buildRecipientItem(
|
builder: (context, snapshot) {
|
||||||
'Budi Santoso', '3201020107030012', 'Selesai', textTheme),
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
_buildRecipientItem(
|
return const Center(child: CircularProgressIndicator());
|
||||||
'Dewi Lestari', '3201020107030013', 'Selesai', textTheme),
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return const Center(child: Text('Gagal memuat data penerima'));
|
||||||
|
}
|
||||||
|
|
||||||
|
final penerimaList = snapshot.data;
|
||||||
|
|
||||||
|
if (penerimaList == null || penerimaList.isEmpty) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.person_off, color: Colors.grey),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Belum ada data penerima'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: penerimaList.length > 3 ? 3 : penerimaList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final penerima = penerimaList[index];
|
||||||
|
final name = penerima['nama_lengkap'] ?? 'Nama tidak tersedia';
|
||||||
|
final nik = penerima['nik'] ?? 'NIK tidak tersedia';
|
||||||
|
final status = penerima['status'] ?? 'AKTIF';
|
||||||
|
final id = penerima['id'] ?? 'ID tidak tersedia';
|
||||||
|
|
||||||
|
return _buildRecipientItem(name, nik, status, id, textTheme);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRecipientItem(
|
Widget _buildRecipientItem(
|
||||||
String name, String nik, String status, TextTheme textTheme) {
|
String name, String nik, String status, String id, TextTheme textTheme) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: AppTheme.primaryGradient,
|
gradient: AppTheme.primaryGradient,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Navigasi ke detail penerima dengan ID statis
|
Get.toNamed(Routes.detailPenerima, arguments: id);
|
||||||
// Kita gunakan ID 1 untuk Siti Rahayu, 2 untuk Budi Santoso, 3 untuk Dewi Lestari
|
|
||||||
String id = "1"; // Default
|
|
||||||
if (nik == "3201020107030011") {
|
|
||||||
id = "2";
|
|
||||||
} else if (nik == "3201020107030012") {
|
|
||||||
id = "3";
|
|
||||||
}
|
|
||||||
Get.toNamed('/daftar-penerima/detail', arguments: id);
|
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: ListTile(
|
child: Padding(
|
||||||
title: Text(
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: Colors.white.withOpacity(0.2),
|
||||||
|
child: const Icon(Icons.person, color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
name,
|
name,
|
||||||
style: textTheme.titleMedium?.copyWith(
|
style: textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
Text(
|
||||||
'NIK: $nik',
|
'NIK: $nik',
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white.withOpacity(0.8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: Container(
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.2),
|
color: Colors.white.withOpacity(0.2),
|
||||||
@ -196,6 +451,8 @@ class DashboardView extends GetView<PetugasDesaController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||||
|
|
||||||
class DetailPenerimaView extends GetView<PenerimaController> {
|
class DetailPenerimaView extends GetView<PenerimaController> {
|
||||||
const DetailPenerimaView({super.key});
|
const DetailPenerimaView({super.key});
|
||||||
@ -54,9 +55,6 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
// Detail informasi penerima
|
// Detail informasi penerima
|
||||||
_buildDetailInfo(penerima),
|
_buildDetailInfo(penerima),
|
||||||
|
|
||||||
// Status penyaluran
|
|
||||||
_buildStatusSection(penerima),
|
|
||||||
|
|
||||||
// Riwayat Penyaluran Bantuan
|
// Riwayat Penyaluran Bantuan
|
||||||
_buildRiwayatPenyaluran(),
|
_buildRiwayatPenyaluran(),
|
||||||
|
|
||||||
@ -127,7 +125,7 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
|
|
||||||
// Nama penerima dengan stroke effect
|
// Nama penerima dengan stroke effect
|
||||||
Text(
|
Text(
|
||||||
penerima['nama'] ?? '',
|
penerima['nama_lengkap'] ?? '',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -203,7 +201,7 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Informasi status aktif
|
// Informasi status aktif
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 4),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -255,27 +253,26 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
children: [
|
children: [
|
||||||
_buildInfoRow('NIK', penerima['nik'] ?? '-'),
|
_buildInfoRow('NIK', penerima['nik'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow('No KK', penerima['noKK'] ?? '-'),
|
_buildInfoRow('No KK', penerima['no_kk'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow('No Handphone', penerima['noHandphone'] ?? '-'),
|
_buildInfoRow('No Handphone', penerima['no_hp'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow('Email', penerima['email'] ?? '-'),
|
_buildInfoRow('Email', penerima['email'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
'Jenis Kelamin', penerima['jenisKelamin'] ?? '-'),
|
'Jenis Kelamin', penerima['jenis_kelamin'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow('Agama', penerima['agama'] ?? '-'),
|
_buildInfoRow('Agama', penerima['agama'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow('Tempat, Tanggal Lahir',
|
|
||||||
penerima['tempatTanggalLahir'] ?? '-'),
|
|
||||||
const Divider(),
|
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
'Alamat Lengkap', penerima['alamatLengkap'] ?? '-'),
|
'Tempat, Tanggal Lahir', penerima['tempat_lahir'] ?? '-'),
|
||||||
|
const Divider(),
|
||||||
|
_buildInfoRow('Alamat Lengkap', penerima['alamat'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow('Pekerjaan', penerima['pekerjaan'] ?? '-'),
|
_buildInfoRow('Pekerjaan', penerima['pekerjaan'] ?? '-'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildInfoRow('Pendidikan Terakhir',
|
_buildInfoRow(
|
||||||
penerima['pendidikanTerakhir'] ?? '-'),
|
'Pendidikan Terakhir', penerima['pendidikan'] ?? '-'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -315,111 +312,6 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatusSection(Map<String, dynamic> penerima) {
|
|
||||||
Color statusColor;
|
|
||||||
IconData statusIcon;
|
|
||||||
|
|
||||||
switch (penerima['status']) {
|
|
||||||
case 'Selesai':
|
|
||||||
statusColor = AppTheme.completedColor;
|
|
||||||
statusIcon = Icons.check_circle;
|
|
||||||
break;
|
|
||||||
case 'Terjadwal':
|
|
||||||
statusColor = AppTheme.processedColor;
|
|
||||||
statusIcon = Icons.event;
|
|
||||||
break;
|
|
||||||
case 'Belum disalurkan':
|
|
||||||
statusColor = AppTheme.warningColor;
|
|
||||||
statusIcon = Icons.pending;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
statusColor = Colors.grey;
|
|
||||||
statusIcon = Icons.help;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Status Penyaluran',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Card(
|
|
||||||
elevation: 2,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: statusColor.withOpacity(0.1),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
statusIcon,
|
|
||||||
color: statusColor,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
penerima['status'] ?? 'Tidak diketahui',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: statusColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (penerima['status'] == 'Belum disalurkan')
|
|
||||||
const Text(
|
|
||||||
'Penerima ini belum dijadwalkan penyaluran bantuan',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (penerima['status'] == 'Terjadwal')
|
|
||||||
const Text(
|
|
||||||
'Penerima ini sudah dijadwalkan penyaluran bantuan',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (penerima['status'] == 'Selesai')
|
|
||||||
const Text(
|
|
||||||
'Penerima ini sudah menerima bantuan',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Widget untuk menampilkan riwayat penyaluran bantuan
|
// Widget untuk menampilkan riwayat penyaluran bantuan
|
||||||
Widget _buildRiwayatPenyaluran() {
|
Widget _buildRiwayatPenyaluran() {
|
||||||
return Container(
|
return Container(
|
||||||
@ -436,6 +328,11 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
|
// Debug prints
|
||||||
|
print('Loading state: ${controller.isLoadingPenyaluran.value}');
|
||||||
|
print(
|
||||||
|
'Daftar penyaluran length: ${controller.daftarPenyaluran.length}');
|
||||||
|
|
||||||
if (controller.isLoadingPenyaluran.value) {
|
if (controller.isLoadingPenyaluran.value) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -475,8 +372,18 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: controller.daftarPenyaluran.length,
|
itemCount: controller.daftarPenyaluran.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
try {
|
||||||
final penyaluran = controller.daftarPenyaluran[index];
|
final penyaluran = controller.daftarPenyaluran[index];
|
||||||
return _buildPenyaluranItem(penyaluran);
|
return _buildPenyaluranItem(penyaluran);
|
||||||
|
} catch (e) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Text('Terjadi kesalahan: $e'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -487,137 +394,98 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
|
|
||||||
// Widget untuk menampilkan item penyaluran bantuan
|
// Widget untuk menampilkan item penyaluran bantuan
|
||||||
Widget _buildPenyaluranItem(Map<String, dynamic> penyaluran) {
|
Widget _buildPenyaluranItem(Map<String, dynamic> penyaluran) {
|
||||||
final DateTime tanggalPenyaluran =
|
// Status penerimaan dengan nilai default
|
||||||
DateTime.parse(penyaluran['tanggal_penyaluran']);
|
final String statusPenerimaan =
|
||||||
final String formattedDate =
|
penyaluran['status_penerimaan'] ?? 'BELUMMENERIMA';
|
||||||
DateFormat('dd MMMM yyyy', 'id_ID').format(tanggalPenyaluran);
|
|
||||||
|
|
||||||
final Color statusColor = penyaluran['status'] == 'TERLAKSANA'
|
final Color statusColor = statusPenerimaan == 'DITERIMA'
|
||||||
? AppTheme.completedColor
|
? AppTheme.completedColor
|
||||||
: penyaluran['status'] == 'DIJADWALKAN'
|
: statusPenerimaan == 'BELUMMENERIMA'
|
||||||
? AppTheme.processedColor
|
? AppTheme.processedColor
|
||||||
: AppTheme.warningColor;
|
: AppTheme.warningColor;
|
||||||
|
|
||||||
final IconData statusIcon = penyaluran['status'] == 'TERLAKSANA'
|
final IconData statusIcon = statusPenerimaan == 'DITERIMA'
|
||||||
? Icons.check_circle
|
? Icons.check_circle
|
||||||
: penyaluran['status'] == 'DIJADWALKAN'
|
: statusPenerimaan == 'BELUMMENERIMA'
|
||||||
? Icons.event
|
? Icons.hourglass_empty
|
||||||
: Icons.pending;
|
: Icons.help;
|
||||||
|
|
||||||
final Map<String, dynamic> stokBantuan =
|
// Data penyaluran bantuan
|
||||||
penyaluran['stok_bantuan'] as Map<String, dynamic>;
|
final Map<String, dynamic> penyaluranBantuan =
|
||||||
|
penyaluran['penyaluran_bantuan'] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
|
// Format tanggal menggunakan DateTimeHelper
|
||||||
|
final tanggalPenerimaan = penyaluran['tanggal_penerimaan'] != null
|
||||||
|
? DateTime.parse(penyaluran['tanggal_penerimaan'])
|
||||||
|
: null;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
elevation: 2,
|
elevation: 3,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Baris atas dengan status dan tanggal
|
// Header dengan nama program dan status
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
// Status penyaluran
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: statusColor.withOpacity(0.1),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
statusIcon,
|
|
||||||
color: statusColor,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
penyaluran['status'] == 'TERLAKSANA'
|
|
||||||
? 'Terlaksana'
|
|
||||||
: penyaluran['status'] == 'DIJADWALKAN'
|
|
||||||
? 'Terjadwal'
|
|
||||||
: 'Menunggu',
|
|
||||||
style: TextStyle(
|
|
||||||
color: statusColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// Tanggal penyaluran
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.calendar_today,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
formattedDate,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(height: 24),
|
|
||||||
|
|
||||||
// Informasi bantuan
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Ikon bantuan
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppTheme.primaryColor.withOpacity(0.1),
|
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: const BorderRadius.only(
|
||||||
),
|
topLeft: Radius.circular(16),
|
||||||
child: const Icon(
|
topRight: Radius.circular(16),
|
||||||
Icons.inventory_2_outlined,
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
size: 24,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
child: Row(
|
||||||
|
children: [
|
||||||
// Detail bantuan
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
stokBantuan['nama'] ?? 'Bantuan',
|
penyaluranBantuan['nama'] ?? 'Program Bantuan',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
if (penyaluranBantuan['deskripsi'] != null)
|
||||||
Text(
|
Padding(
|
||||||
'${stokBantuan['jenis'] ?? 'Umum'} • ${stokBantuan['kuantitas'] ?? '1 Paket'}',
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: Text(
|
||||||
|
penyaluranBantuan['deskripsi'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[600],
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
color: Colors.grey[600],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: statusColor.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
statusIcon,
|
||||||
|
size: 16,
|
||||||
|
color: statusColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
penyaluran['keterangan'] ?? '',
|
statusPenerimaan,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
|
color: statusColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -626,16 +494,72 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Informasi waktu dan jumlah
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildInfoItem(
|
||||||
|
Icons.calendar_today,
|
||||||
|
'Tanggal Penerimaan',
|
||||||
|
DateTimeHelper.formatDateTime(tanggalPenerimaan),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildInfoItem(
|
||||||
|
Icons.inventory_2,
|
||||||
|
'Jumlah Diterima',
|
||||||
|
'${penyaluran['jumlah_bantuan'] ?? '0'} paket',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
// Tampilkan bukti penyaluran jika ada dan status TERLAKSANA
|
const SizedBox(height: 16),
|
||||||
if (penyaluran['status'] == 'TERLAKSANA' &&
|
|
||||||
penyaluran['bukti_penyaluran'] != null)
|
if (penyaluranBantuan['lokasi_penyaluran'] != null) ...[
|
||||||
Column(
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildInfoItem(
|
||||||
|
Icons.location_on,
|
||||||
|
'Lokasi Penyaluran',
|
||||||
|
penyaluranBantuan['lokasi_penyaluran']['nama'] ??
|
||||||
|
'Tidak tersedia',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildInfoItem(
|
||||||
|
Icons.map,
|
||||||
|
'Alamat Lokasi',
|
||||||
|
penyaluranBantuan['lokasi_penyaluran']
|
||||||
|
['alamat_lengkap'] ??
|
||||||
|
'Tidak tersedia',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Bukti penerimaan dan tanda tangan
|
||||||
|
if (penyaluran['bukti_penerimaan'] != null ||
|
||||||
|
penyaluran['tanda_tangan'] != null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (penyaluran['bukti_penerimaan'] != null)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Divider(height: 24),
|
|
||||||
const Text(
|
const Text(
|
||||||
'Bukti Penyaluran',
|
'Bukti Penerimaan',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@ -644,17 +568,23 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Image.asset(
|
child: Image.network(
|
||||||
penyaluran['bukti_penyaluran'],
|
penyaluran['bukti_penerimaan'],
|
||||||
height: 120,
|
height: 100,
|
||||||
|
width: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 120,
|
height: 100,
|
||||||
width: double.infinity,
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Text('Gambar tidak tersedia'),
|
child: Icon(
|
||||||
|
Icons.broken_image,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -662,9 +592,157 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
if (penyaluran['bukti_penerimaan'] != null &&
|
||||||
|
penyaluran['tanda_tangan'] != null)
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
if (penyaluran['tanda_tangan'] != null)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Tanda Tangan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Image.network(
|
||||||
|
penyaluran['tanda_tangan'],
|
||||||
|
height: 100,
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Container(
|
||||||
|
height: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.broken_image,
|
||||||
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// QR Code
|
||||||
|
if (penyaluran['qr_code_hash'] != null) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'QR Code Verifikasi',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.2),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Image.network(
|
||||||
|
'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${penyaluran['qr_code_hash']}',
|
||||||
|
height: 120,
|
||||||
|
width: 120,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Container(
|
||||||
|
height: 120,
|
||||||
|
width: 120,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.qr_code_2,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoItem(IconData icon, String label, String value) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 20,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1744,8 +1744,8 @@ class DetailPenyaluranPage extends StatelessWidget {
|
|||||||
if (status == 'DITERIMA') {
|
if (status == 'DITERIMA') {
|
||||||
return item.statusPenerimaan?.toUpperCase() == 'DITERIMA';
|
return item.statusPenerimaan?.toUpperCase() == 'DITERIMA';
|
||||||
} else {
|
} else {
|
||||||
// Filter untuk yang belum menerima
|
// Semua status selain DITERIMA dianggap sebagai BELUMMENERIMA
|
||||||
return item.statusPenerimaan?.toUpperCase() != 'DITERIMA';
|
return item.statusPenerimaan?.toUpperCase() == 'BELUMMENERIMA';
|
||||||
}
|
}
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,9 @@ class ProfileController extends GetxController {
|
|||||||
Future<void> loadUserData() async {
|
Future<void> loadUserData() async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
|
// Hapus cache data user sebelum mengambil data baru
|
||||||
|
_supabaseService.clearUserProfileCache();
|
||||||
|
|
||||||
// Mendapatkan data user dari service
|
// Mendapatkan data user dari service
|
||||||
final userData = await _supabaseService.getUserProfile();
|
final userData = await _supabaseService.getUserProfile();
|
||||||
if (userData != null) {
|
if (userData != null) {
|
||||||
@ -67,9 +70,15 @@ class ProfileController extends GetxController {
|
|||||||
if (roleData.value?['foto_profil'] != null) {
|
if (roleData.value?['foto_profil'] != null) {
|
||||||
fotoProfil.value = roleData.value?['foto_profil'] ?? '';
|
fotoProfil.value = roleData.value?['foto_profil'] ?? '';
|
||||||
print(fotoProfil.value);
|
print(fotoProfil.value);
|
||||||
|
} else {
|
||||||
|
// Reset foto profil jika tidak ada data
|
||||||
|
fotoProfil.value = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Muat ulang data user di AuthController untuk memastikan konsistensi
|
||||||
|
await _authController.refreshUserData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
@ -140,6 +149,95 @@ class ProfileController extends GetxController {
|
|||||||
// Metode untuk menghapus foto profil
|
// Metode untuk menghapus foto profil
|
||||||
void clearFotoProfil() {
|
void clearFotoProfil() {
|
||||||
fotoProfilPath.value = '';
|
fotoProfilPath.value = '';
|
||||||
|
if (isEditing.value) {
|
||||||
|
// Cek jika user adalah warga
|
||||||
|
if (user.value?.role?.toLowerCase() == 'warga') {
|
||||||
|
Get.snackbar(
|
||||||
|
'Tidak Diizinkan',
|
||||||
|
'Data warga hanya dapat diubah melalui aplikasi verifikasi data warga. Silakan hubungi petugas desa untuk perubahan data.',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.amber,
|
||||||
|
colorText: Colors.black,
|
||||||
|
duration: const Duration(seconds: 5),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tandai bahwa foto profil akan dihapus saat menyimpan perubahan
|
||||||
|
Get.dialog(
|
||||||
|
AlertDialog(
|
||||||
|
title: const Text('Konfirmasi'),
|
||||||
|
content: const Text('Apakah Anda yakin ingin menghapus foto profil?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('Batal'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Get.back();
|
||||||
|
try {
|
||||||
|
final userData = user.value;
|
||||||
|
if (userData == null) return;
|
||||||
|
|
||||||
|
// Update data profil dengan foto kosong
|
||||||
|
switch (userData.role?.toLowerCase() ?? 'unknown') {
|
||||||
|
case 'donatur':
|
||||||
|
await _supabaseService.updateDonaturProfile(
|
||||||
|
userId: userData.id,
|
||||||
|
nama: nameController.text,
|
||||||
|
noHp: phoneController.text,
|
||||||
|
email: emailController.text,
|
||||||
|
fotoProfil:
|
||||||
|
'', // Kosongkan foto profil dengan string kosong
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'petugas_desa':
|
||||||
|
await _supabaseService.updatePetugasDesaProfile(
|
||||||
|
userId: userData.id,
|
||||||
|
nama: nameController.text,
|
||||||
|
noHp: phoneController.text,
|
||||||
|
email: emailController.text,
|
||||||
|
fotoProfil:
|
||||||
|
'', // Kosongkan foto profil dengan string kosong
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hapus cache dan refresh data
|
||||||
|
_supabaseService.clearUserProfileCache();
|
||||||
|
fotoProfil.value = '';
|
||||||
|
await _authController.refreshUserData();
|
||||||
|
await loadUserData();
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Foto profil berhasil dihapus',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menghapus foto profil: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
child: const Text('Hapus'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mengupload foto profil
|
// Metode untuk mengupload foto profil
|
||||||
@ -179,11 +277,34 @@ class ProfileController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final userData = user.value;
|
||||||
|
if (userData == null) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Data user tidak ditemukan',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek jika user adalah warga, maka tidak diperbolehkan mengubah profil
|
||||||
|
if (userData.role?.toLowerCase() == 'warga') {
|
||||||
|
Get.snackbar(
|
||||||
|
'Tidak Diizinkan',
|
||||||
|
'Data warga hanya dapat diubah melalui aplikasi verifikasi data warga. Silakan hubungi petugas desa untuk perubahan data.',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.amber,
|
||||||
|
colorText: Colors.black,
|
||||||
|
duration: const Duration(seconds: 5),
|
||||||
|
);
|
||||||
|
isEditing.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
final userData = user.value;
|
|
||||||
if (userData == null) throw 'Data user tidak ditemukan';
|
|
||||||
|
|
||||||
// Upload foto profil jika ada
|
// Upload foto profil jika ada
|
||||||
String? fotoProfilUrl;
|
String? fotoProfilUrl;
|
||||||
if (fotoProfilPath.isNotEmpty) {
|
if (fotoProfilPath.isNotEmpty) {
|
||||||
@ -195,15 +316,6 @@ class ProfileController extends GetxController {
|
|||||||
|
|
||||||
// Update data sesuai role
|
// Update data sesuai role
|
||||||
switch (userData.role?.toLowerCase() ?? 'unknown') {
|
switch (userData.role?.toLowerCase() ?? 'unknown') {
|
||||||
case 'warga':
|
|
||||||
await _supabaseService.updateWargaProfile(
|
|
||||||
userId: userData.id,
|
|
||||||
namaLengkap: nameController.text,
|
|
||||||
noHp: phoneController.text,
|
|
||||||
email: emailController.text,
|
|
||||||
fotoProfil: fotoProfilUrl,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'donatur':
|
case 'donatur':
|
||||||
await _supabaseService.updateDonaturProfile(
|
await _supabaseService.updateDonaturProfile(
|
||||||
userId: userData.id,
|
userId: userData.id,
|
||||||
@ -226,14 +338,17 @@ class ProfileController extends GetxController {
|
|||||||
throw 'Role tidak valid';
|
throw 'Role tidak valid';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh data lokal
|
// Hapus cache data profil sebelum refresh
|
||||||
await loadUserData();
|
_supabaseService.clearUserProfileCache();
|
||||||
|
|
||||||
|
// Reset path foto setelah update
|
||||||
|
fotoProfilPath.value = '';
|
||||||
|
|
||||||
// Refresh data di AuthController untuk menyebarkan perubahan ke seluruh aplikasi
|
// Refresh data di AuthController untuk menyebarkan perubahan ke seluruh aplikasi
|
||||||
await _authController.refreshUserData();
|
await _authController.refreshUserData();
|
||||||
|
|
||||||
// Reset path foto setelah update
|
// Refresh data lokal
|
||||||
fotoProfilPath.value = '';
|
await loadUserData();
|
||||||
|
|
||||||
// Keluar dari mode edit
|
// Keluar dari mode edit
|
||||||
isEditing.value = false;
|
isEditing.value = false;
|
||||||
|
@ -58,6 +58,8 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
title: const Text('Profil'),
|
title: const Text('Profil'),
|
||||||
actions: [
|
actions: [
|
||||||
Obx(() {
|
Obx(() {
|
||||||
|
// Hanya tampilkan tombol edit jika user bukan warga
|
||||||
|
if (controller.user.value?.role?.toLowerCase() != 'warga') {
|
||||||
if (controller.isEditing.value) {
|
if (controller.isEditing.value) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
@ -69,6 +71,9 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
onPressed: controller.toggleEditMode,
|
onPressed: controller.toggleEditMode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return const SizedBox
|
||||||
|
.shrink(); // Jangan tampilkan apapun untuk warga
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -122,7 +127,8 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
|
|
||||||
// Tombol edit foto (hanya muncul dalam mode edit)
|
// Tombol edit foto (hanya muncul dalam mode edit)
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if (controller.isEditing.value) {
|
if (controller.isEditing.value &&
|
||||||
|
controller.user.value?.role?.toLowerCase() != 'warga') {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -164,13 +170,36 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Obx(() => Text(
|
// Menggunakan Obx untuk reaktif memperbarui nama
|
||||||
controller.user.value?.name ?? 'Pengguna',
|
Obx(() {
|
||||||
|
// Mengambil nama dari roleData jika ada, atau dari user
|
||||||
|
String displayName = '';
|
||||||
|
|
||||||
|
final roleDataValue = controller.roleData.value;
|
||||||
|
final userValue = controller.user.value;
|
||||||
|
|
||||||
|
if (roleDataValue != null) {
|
||||||
|
// Prioritaskan data dari roleData karena lebih spesifik
|
||||||
|
if (roleDataValue['nama_lengkap'] != null) {
|
||||||
|
displayName = roleDataValue['nama_lengkap'];
|
||||||
|
} else if (roleDataValue['nama'] != null) {
|
||||||
|
displayName = roleDataValue['nama'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gunakan data dari user jika tidak ada di roleData
|
||||||
|
if (displayName.isEmpty && userValue != null) {
|
||||||
|
displayName = userValue.name ?? 'Pengguna';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text(
|
||||||
|
displayName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
)),
|
);
|
||||||
|
}),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final role = controller.user.value?.role;
|
final role = controller.user.value?.role;
|
||||||
@ -235,8 +264,10 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
width: 120,
|
width: 120,
|
||||||
height: 120,
|
height: 120,
|
||||||
errorBuilder: (context, error, stackTrace) =>
|
errorBuilder: (context, error, stackTrace) {
|
||||||
_buildDefaultProfileImage(),
|
print('Error loading local image: $error');
|
||||||
|
return _buildDefaultProfileImage();
|
||||||
|
},
|
||||||
)
|
)
|
||||||
: Image.network(
|
: Image.network(
|
||||||
imagePath,
|
imagePath,
|
||||||
@ -254,8 +285,11 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
errorBuilder: (context, error, stackTrace) =>
|
errorBuilder: (context, error, stackTrace) {
|
||||||
_buildDefaultProfileImage(),
|
print('Error loading network image: $error');
|
||||||
|
print('Failed image URL: $imagePath');
|
||||||
|
return _buildDefaultProfileImage();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -321,10 +355,34 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
return Obx(() {
|
return Obx(() {
|
||||||
final isEditing = controller.isEditing.value;
|
final isEditing = controller.isEditing.value;
|
||||||
final user = controller.user.value;
|
final user = controller.user.value;
|
||||||
|
// Form tidak bisa diedit jika usernya warga
|
||||||
|
final bool canEdit = isEditing && user?.role?.toLowerCase() != 'warga';
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.person_outline, color: AppTheme.primaryColor),
|
||||||
|
const SizedBox(width: 10),
|
||||||
const Text(
|
const Text(
|
||||||
'Informasi Pribadi',
|
'Informasi Pribadi',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -332,6 +390,38 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Menampilkan notifikasi khusus untuk warga
|
||||||
|
if (user?.role?.toLowerCase() == 'warga') ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.amber.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.amber.withOpacity(0.5)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline,
|
||||||
|
color: Colors.amber[800], size: 24),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Data warga hanya dapat diubah melalui aplikasi verifikasi data warga. Silakan hubungi petugas desa untuk perubahan data.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.amber[900],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Nama
|
// Nama
|
||||||
@ -339,9 +429,11 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
controller: controller.nameController,
|
controller: controller.nameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Nama Lengkap',
|
labelText: 'Nama Lengkap',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
prefixIcon: Icon(Icons.person),
|
prefixIcon: Icon(Icons.person),
|
||||||
enabled: isEditing,
|
enabled: canEdit,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@ -351,7 +443,9 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
controller: controller.emailController,
|
controller: controller.emailController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Email',
|
labelText: 'Email',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
prefixIcon: Icon(Icons.email),
|
prefixIcon: Icon(Icons.email),
|
||||||
enabled: false, // Email tidak bisa diubah
|
enabled: false, // Email tidak bisa diubah
|
||||||
),
|
),
|
||||||
@ -364,50 +458,66 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
controller: controller.phoneController,
|
controller: controller.phoneController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Nomor Telepon',
|
labelText: 'Nomor Telepon',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
prefixIcon: Icon(Icons.phone),
|
prefixIcon: Icon(Icons.phone),
|
||||||
enabled: isEditing,
|
enabled: canEdit,
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Informasi tambahan sesuai role
|
// Informasi tambahan sesuai role
|
||||||
if (user != null) ...[
|
if (user != null) ...[
|
||||||
if (user.role?.toLowerCase() == 'warga') ...[
|
if (user.role?.toLowerCase() == 'warga') ...[
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.infoColor.withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border:
|
||||||
|
Border.all(color: AppTheme.infoColor.withOpacity(0.2)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.person_pin_circle,
|
||||||
|
color: AppTheme.infoColor),
|
||||||
|
const SizedBox(width: 10),
|
||||||
const Text(
|
const Text(
|
||||||
'Informasi Warga',
|
'Informasi Lainnya',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final roleData = controller.roleData.value;
|
final roleData = controller.roleData.value;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow(
|
_buildInfoRow(Icons.perm_identity, 'NIK',
|
||||||
Icons.perm_identity, 'NIK', roleData?['nik'] ?? '-'),
|
roleData?['nik'] ?? '-'),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(Icons.wc, 'Jenis Kelamin',
|
_buildInfoRow(Icons.wc, 'Jenis Kelamin',
|
||||||
roleData?['jenis_kelamin'] ?? '-'),
|
roleData?['jenis_kelamin'] ?? '-'),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
Icons.home, 'Alamat', roleData?['alamat'] ?? '-'),
|
Icons.home, 'Alamat', roleData?['alamat'] ?? '-'),
|
||||||
if (user.desa != null) ...[
|
if (user.desa != null) ...[
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
Icons.location_city, 'Desa', user.desa!.nama),
|
Icons.location_city, 'Desa', user.desa!.nama),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
||||||
user.desa!.kecamatan ?? ''),
|
user.desa!.kecamatan ?? ''),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
||||||
user.desa!.kabupaten ?? ''),
|
user.desa!.kabupaten ?? ''),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(Icons.location_on, 'Provinsi',
|
_buildInfoRow(Icons.location_on, 'Provinsi',
|
||||||
user.desa!.provinsi ?? ''),
|
user.desa!.provinsi ?? ''),
|
||||||
],
|
],
|
||||||
@ -415,8 +525,27 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
if (user.role?.toLowerCase() == 'donatur') ...[
|
if (user.role?.toLowerCase() == 'donatur') ...[
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.successColor.withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border:
|
||||||
|
Border.all(color: AppTheme.successColor.withOpacity(0.2)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.volunteer_activism,
|
||||||
|
color: AppTheme.successColor),
|
||||||
|
const SizedBox(width: 10),
|
||||||
const Text(
|
const Text(
|
||||||
'Informasi Donatur',
|
'Informasi Donatur',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -424,6 +553,8 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final roleData = controller.roleData.value;
|
final roleData = controller.roleData.value;
|
||||||
@ -432,15 +563,32 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
children: [
|
children: [
|
||||||
_buildInfoRow(Icons.business, 'Instansi',
|
_buildInfoRow(Icons.business, 'Instansi',
|
||||||
roleData?['instansi'] ?? '-'),
|
roleData?['instansi'] ?? '-'),
|
||||||
const SizedBox(height: 8),
|
_buildInfoRow(Icons.work, 'Jabatan',
|
||||||
_buildInfoRow(
|
roleData?['jabatan'] ?? '-'),
|
||||||
Icons.work, 'Jabatan', roleData?['jabatan'] ?? '-'),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
if (user.role?.toLowerCase() == 'petugas_desa') ...[
|
if (user.role?.toLowerCase() == 'petugas_desa') ...[
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryColor.withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border:
|
||||||
|
Border.all(color: AppTheme.primaryColor.withOpacity(0.2)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.badge, color: AppTheme.primaryColor),
|
||||||
|
const SizedBox(width: 10),
|
||||||
const Text(
|
const Text(
|
||||||
'Informasi Petugas Desa',
|
'Informasi Petugas Desa',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -448,24 +596,23 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final roleData = controller.roleData.value;
|
final roleData = controller.roleData.value;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow(Icons.badge, 'NIP', roleData?['nip'] ?? '-'),
|
_buildInfoRow(
|
||||||
|
Icons.badge, 'NIP', roleData?['nip'] ?? '-'),
|
||||||
if (user.desa != null) ...[
|
if (user.desa != null) ...[
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
Icons.location_city, 'Desa', user.desa!.nama),
|
Icons.location_city, 'Desa', user.desa!.nama),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
||||||
user.desa!.kecamatan ?? ''),
|
user.desa!.kecamatan ?? ''),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
||||||
user.desa!.kabupaten ?? ''),
|
user.desa!.kabupaten ?? ''),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoRow(Icons.location_on, 'Provinsi',
|
_buildInfoRow(Icons.location_on, 'Provinsi',
|
||||||
user.desa!.provinsi ?? ''),
|
user.desa!.provinsi ?? ''),
|
||||||
],
|
],
|
||||||
@ -473,6 +620,9 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -480,9 +630,28 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPasswordSection(BuildContext context) {
|
Widget _buildPasswordSection(BuildContext context) {
|
||||||
return Column(
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.security, color: AppTheme.primaryColor),
|
||||||
|
const SizedBox(width: 10),
|
||||||
const Text(
|
const Text(
|
||||||
'Keamanan',
|
'Keamanan',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -490,6 +659,8 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () => _showChangePasswordDialog(context),
|
onPressed: () => _showChangePasswordDialog(context),
|
||||||
@ -499,9 +670,14 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
backgroundColor: AppTheme.primaryColor,
|
backgroundColor: AppTheme.primaryColor,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
minimumSize: const Size(double.infinity, 50),
|
minimumSize: const Size(double.infinity, 50),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,34 +688,52 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title: const Text('Ubah Password'),
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.lock_reset, color: AppTheme.primaryColor),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Text('Ubah Password'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: currentPasswordController,
|
controller: currentPasswordController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password Saat Ini',
|
labelText: 'Password Saat Ini',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.key),
|
||||||
),
|
),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: newPasswordController,
|
controller: newPasswordController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password Baru',
|
labelText: 'Password Baru',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.lock),
|
||||||
),
|
),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: confirmPasswordController,
|
controller: confirmPasswordController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Konfirmasi Password Baru',
|
labelText: 'Konfirmasi Password Baru',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.lock_clock),
|
||||||
),
|
),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
@ -550,6 +744,9 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
child: const Text('Batal'),
|
child: const Text('Batal'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.grey[700],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -561,6 +758,11 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppTheme.primaryColor,
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
),
|
),
|
||||||
child: const Text('Simpan'),
|
child: const Text('Simpan'),
|
||||||
),
|
),
|
||||||
@ -570,21 +772,65 @@ class ProfileView extends GetView<ProfileController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInfoRow(IconData icon, String label, String value) {
|
Widget _buildInfoRow(IconData icon, String label, String value) {
|
||||||
return Row(
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 22,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 18, color: Colors.grey[700]),
|
Text(
|
||||||
const SizedBox(width: 8),
|
label,
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'$label: $value',
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 12,
|
||||||
color: Colors.grey[700],
|
color: Colors.grey[600],
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
value.isEmpty ? '-' : value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,16 @@ class WargaDashboardController extends GetxController {
|
|||||||
super.onInit();
|
super.onInit();
|
||||||
fetchData();
|
fetchData();
|
||||||
loadUserData();
|
loadUserData();
|
||||||
|
|
||||||
|
// Atau gunakan timer untuk refresh data secara periodik
|
||||||
|
// Timer.periodic(Duration(seconds: 60), (_) => loadUserData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onReady() {
|
||||||
|
super.onReady();
|
||||||
|
// Perbarui data user dan foto profil saat halaman siap
|
||||||
|
loadUserData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadUserData() {
|
void loadUserData() {
|
||||||
@ -133,7 +143,7 @@ class WargaDashboardController extends GetxController {
|
|||||||
wargaData.fotoProfil != null &&
|
wargaData.fotoProfil != null &&
|
||||||
wargaData.fotoProfil!.isNotEmpty) {
|
wargaData.fotoProfil!.isNotEmpty) {
|
||||||
fotoProfil.value = wargaData.fotoProfil!;
|
fotoProfil.value = wargaData.fotoProfil!;
|
||||||
print('DEBUG WARGA: Foto profil: ${fotoProfil.value}');
|
print('DEBUG WARGA: Foto profil dari roleData: ${fotoProfil.value}');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print('DEBUG WARGA: User bukan warga');
|
print('DEBUG WARGA: User bukan warga');
|
||||||
@ -142,11 +152,9 @@ class WargaDashboardController extends GetxController {
|
|||||||
print('DEBUG WARGA: userData null');
|
print('DEBUG WARGA: userData null');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cek dan ambil foto profil jika belum ada
|
// Ambil foto profil dari database
|
||||||
if (fotoProfil.isEmpty) {
|
|
||||||
_fetchProfilePhoto();
|
_fetchProfilePhoto();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mengambil foto profil
|
// Metode untuk mengambil foto profil
|
||||||
Future<void> _fetchProfilePhoto() async {
|
Future<void> _fetchProfilePhoto() async {
|
||||||
@ -156,12 +164,14 @@ class WargaDashboardController extends GetxController {
|
|||||||
final wargaData = await _supabaseService.client
|
final wargaData = await _supabaseService.client
|
||||||
.from('warga')
|
.from('warga')
|
||||||
.select('foto_profil')
|
.select('foto_profil')
|
||||||
.eq('user_id', user!.id)
|
.eq('id', user!.id) // Menggunakan id, bukan user_id
|
||||||
.single();
|
.maybeSingle();
|
||||||
|
|
||||||
if (wargaData != null && wargaData['foto_profil'] != null) {
|
if (wargaData != null && wargaData['foto_profil'] != null) {
|
||||||
fotoProfil.value = wargaData['foto_profil'];
|
fotoProfil.value = wargaData['foto_profil'];
|
||||||
print('DEBUG WARGA: Foto profil dari API: ${fotoProfil.value}');
|
print('DEBUG WARGA: Foto profil dari API: ${fotoProfil.value}');
|
||||||
|
} else {
|
||||||
|
print('DEBUG WARGA: Foto profil tidak ditemukan atau null');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching profile photo: $e');
|
print('Error fetching profile photo: $e');
|
||||||
@ -586,4 +596,13 @@ class WargaDashboardController extends GetxController {
|
|||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk refresh data setelah update profil atau kembali ke halaman
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
print('DEBUG WARGA: Memulai refresh data...');
|
||||||
|
await _authController.refreshUserData(); // Refresh data dari server
|
||||||
|
loadUserData(); // Muat ulang data ke variabel lokal
|
||||||
|
fetchData(); // Ambil data terkait lainnya
|
||||||
|
print('DEBUG WARGA: Refresh data selesai');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
children: [
|
children: [
|
||||||
_buildWelcomeSection(),
|
_buildWelcomeSection(),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
_buildStatisticSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
_buildPenerimaanSummary(),
|
_buildPenerimaanSummary(),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
_buildRecentPenerimaan(),
|
_buildRecentPenerimaan(),
|
||||||
@ -296,6 +298,144 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildStatisticSection() {
|
||||||
|
// Data untuk statistik
|
||||||
|
final totalBantuan = controller.penerimaPenyaluran.length;
|
||||||
|
final totalDiterima = controller.penerimaPenyaluran
|
||||||
|
.where((item) => item.statusPenerimaan == 'DITERIMA')
|
||||||
|
.length;
|
||||||
|
final totalBelumMenerima = controller.penerimaPenyaluran
|
||||||
|
.where((item) => item.statusPenerimaan == 'BELUMMENERIMA')
|
||||||
|
.length;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SectionHeader(
|
||||||
|
title: 'Statistik Bantuan',
|
||||||
|
titleStyle: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatisticCard(
|
||||||
|
icon: Icons.check_circle,
|
||||||
|
color: Colors.green,
|
||||||
|
title: 'Diterima',
|
||||||
|
value: totalDiterima.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatisticCard(
|
||||||
|
icon: Icons.do_not_disturb,
|
||||||
|
color: Colors.red,
|
||||||
|
title: 'Belum Menerima',
|
||||||
|
value: totalBelumMenerima.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Progress bar untuk persentase bantuan yang diterima
|
||||||
|
if (totalBantuan > 0) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Kemajuan Penerimaan Bantuan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: totalDiterima / totalBantuan,
|
||||||
|
minHeight: 12,
|
||||||
|
backgroundColor: Colors.grey.shade200,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'${(totalDiterima / totalBantuan * 100).toStringAsFixed(0)}% bantuan telah diterima',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatisticCard({
|
||||||
|
required IconData icon,
|
||||||
|
required Color color,
|
||||||
|
required String title,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 20,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildPenerimaanSummary() {
|
Widget _buildPenerimaanSummary() {
|
||||||
final currencyFormat = NumberFormat.currency(
|
final currencyFormat = NumberFormat.currency(
|
||||||
locale: 'id',
|
locale: 'id',
|
||||||
@ -324,24 +464,52 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Container(
|
||||||
elevation: 2,
|
decoration: BoxDecoration(
|
||||||
shape: RoundedRectangleBorder(
|
borderRadius: BorderRadius.circular(20),
|
||||||
borderRadius: BorderRadius.circular(16),
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.blue.shade50, Colors.white],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blue.withOpacity(0.1),
|
||||||
|
blurRadius: 15,
|
||||||
|
offset: const Offset(0, 5),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
title: 'Ringkasan Bantuan',
|
title: 'Ringkasan Bantuan',
|
||||||
titleStyle: const TextStyle(
|
titleStyle: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
),
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blue.withOpacity(0.05),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
if (totalUang > 0)
|
if (totalUang > 0)
|
||||||
_buildSummaryItem(
|
_buildSummaryItem(
|
||||||
icon: Icons.attach_money,
|
icon: Icons.attach_money,
|
||||||
@ -350,15 +518,28 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
value: currencyFormat.format(totalUang),
|
value: currencyFormat.format(totalUang),
|
||||||
),
|
),
|
||||||
if (totalNonUang.isNotEmpty) ...[
|
if (totalNonUang.isNotEmpty) ...[
|
||||||
if (totalUang > 0) const SizedBox(height: 12),
|
if (totalUang > 0)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: Divider(height: 1),
|
||||||
|
),
|
||||||
...totalNonUang.entries.map((entry) {
|
...totalNonUang.entries.map((entry) {
|
||||||
return _buildSummaryItem(
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildSummaryItem(
|
||||||
icon: Icons.inventory_2,
|
icon: Icons.inventory_2,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
title: 'Total Bantuan ${entry.key}',
|
title: 'Total Bantuan ${entry.key}',
|
||||||
value: '${entry.value} ${entry.key}',
|
value: '${entry.value} ${entry.key}',
|
||||||
|
),
|
||||||
|
if (entry != totalNonUang.entries.last)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: Divider(height: 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}),
|
}).toList(),
|
||||||
],
|
],
|
||||||
if (totalUang == 0 && totalNonUang.isEmpty)
|
if (totalUang == 0 && totalNonUang.isEmpty)
|
||||||
_buildSummaryItem(
|
_buildSummaryItem(
|
||||||
@ -370,6 +551,9 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,14 +606,82 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
|
|
||||||
Widget _buildRecentPenerimaan() {
|
Widget _buildRecentPenerimaan() {
|
||||||
if (controller.penerimaPenyaluran.isEmpty) {
|
if (controller.penerimaPenyaluran.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SectionHeader(
|
||||||
|
title: 'Bantuan Terbaru',
|
||||||
|
titleStyle: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: Colors.grey.shade200),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
size: 48,
|
||||||
|
color: Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Belum Ada Bantuan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Data bantuan akan muncul di sini ketika Anda menerima bantuan.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final maxItems = controller.penerimaPenyaluran.length > 2
|
final maxItems = controller.penerimaPenyaluran.length > 2
|
||||||
? 2
|
? 2
|
||||||
: controller.penerimaPenyaluran.length;
|
: controller.penerimaPenyaluran.length;
|
||||||
|
|
||||||
return Column(
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.05),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
@ -438,7 +690,14 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
onViewAll: () {
|
onViewAll: () {
|
||||||
Get.toNamed(Routes.wargaPenerimaan);
|
Get.toNamed(Routes.wargaPenerimaan);
|
||||||
},
|
},
|
||||||
|
titleStyle: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
),
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
@ -460,15 +719,27 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
),
|
),
|
||||||
if (controller.penerimaPenyaluran.length > 2)
|
if (controller.penerimaPenyaluran.length > 2)
|
||||||
Center(
|
Center(
|
||||||
child: TextButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.toNamed('/warga-penerimaan');
|
Get.toNamed(Routes.wargaPenerimaan);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.list),
|
icon: const Icon(Icons.list),
|
||||||
label: const Text('Lihat Semua Bantuan'),
|
label: const Text('Lihat Semua Bantuan'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,8 +328,8 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
StatusBadge(
|
StatusBadge(
|
||||||
status:
|
status: penyaluran.statusPenerimaan ??
|
||||||
penyaluran.statusPenerimaan ?? 'MENUNGGU',
|
'BELUMMENERIMA',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
@ -423,7 +423,7 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
StatusBadge(
|
StatusBadge(
|
||||||
status: penyaluran.statusPenerimaan ?? 'MENUNGGU',
|
status: penyaluran.statusPenerimaan ?? 'BELUMMENERIMA',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
|
@ -4,16 +4,28 @@ import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_con
|
|||||||
import 'package:penyaluran_app/app/modules/warga/views/warga_dashboard_view.dart';
|
import 'package:penyaluran_app/app/modules/warga/views/warga_dashboard_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/warga/views/warga_penerimaan_view.dart';
|
import 'package:penyaluran_app/app/modules/warga/views/warga_penerimaan_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/warga/views/warga_pengaduan_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/widgets/app_bottom_navigation_bar.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
class WargaView extends GetView<WargaDashboardController> {
|
class WargaView extends GetView<WargaDashboardController> {
|
||||||
const WargaView({super.key});
|
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
WargaView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
// Tambahkan listener untuk refresh data saat fokus didapatkan kembali
|
||||||
|
// misalnya ketika kembali dari halaman profil
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final focusNode = FocusNode();
|
||||||
|
FocusScope.of(context).requestFocus(focusNode);
|
||||||
|
focusNode.addListener(() {
|
||||||
|
if (focusNode.hasFocus) {
|
||||||
|
print('DEBUG WARGA: Halaman mendapatkan fokus, memuat ulang data');
|
||||||
|
controller.refreshData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: scaffoldKey,
|
key: scaffoldKey,
|
||||||
@ -81,40 +93,7 @@ class WargaView extends GetView<WargaDashboardController> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
drawer: Obx(() => AppDrawer(
|
drawer: _buildDrawer(context),
|
||||||
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: 'Penerimaan',
|
|
||||||
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.description_outlined,
|
|
||||||
title: 'Laporan Penyaluran',
|
|
||||||
onTap: () => Get.toNamed('/laporan-penyaluran'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
body: Obx(() {
|
body: Obx(() {
|
||||||
switch (controller.activeTabIndex.value) {
|
switch (controller.activeTabIndex.value) {
|
||||||
case 0:
|
case 0:
|
||||||
@ -158,4 +137,276 @@ class WargaView extends GetView<WargaDashboardController> {
|
|||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDrawer(BuildContext context) {
|
||||||
|
// Muat ulang data foto profil ketika drawer dibuka
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (controller.fotoProfil.isEmpty) {
|
||||||
|
controller.loadUserData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 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
|
||||||
|
? Icon(
|
||||||
|
Icons.person,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Halo,',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
controller.nama,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 22,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Warga',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
controller.desa ?? 'Tidak ada desa',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: [
|
||||||
|
_buildMenuCategory('Menu Utama'),
|
||||||
|
Obx(() => _buildMenuItem(
|
||||||
|
icon: Icons.dashboard_outlined,
|
||||||
|
activeIcon: Icons.dashboard,
|
||||||
|
title: 'Dashboard',
|
||||||
|
isSelected: controller.activeTabIndex.value == 0,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
controller.changeTab(0);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Obx(() => _buildMenuItem(
|
||||||
|
icon: Icons.volunteer_activism_outlined,
|
||||||
|
activeIcon: Icons.volunteer_activism,
|
||||||
|
title: 'Penerimaan',
|
||||||
|
isSelected: controller.activeTabIndex.value == 1,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
controller.changeTab(1);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Obx(() => _buildMenuItem(
|
||||||
|
icon: Icons.report_problem_outlined,
|
||||||
|
activeIcon: Icons.report_problem,
|
||||||
|
title: 'Pengaduan',
|
||||||
|
isSelected: controller.activeTabIndex.value == 2,
|
||||||
|
badge: controller.totalPengaduanProses.value > 0
|
||||||
|
? controller.totalPengaduanProses.value.toString()
|
||||||
|
: null,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
controller.changeTab(2);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
_buildMenuCategory('Pengaturan'),
|
||||||
|
_buildMenuItem(
|
||||||
|
icon: Icons.person_outline,
|
||||||
|
activeIcon: Icons.person,
|
||||||
|
title: 'Profil',
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Get.toNamed('/profile');
|
||||||
|
// Refresh data ketika kembali dari profil
|
||||||
|
controller.refreshData();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildMenuItem(
|
||||||
|
icon: Icons.logout,
|
||||||
|
title: 'Keluar',
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
controller.logout();
|
||||||
|
},
|
||||||
|
isLogout: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Text(
|
||||||
|
'© ${DateTime.now().year} Aplikasi Penyaluran Bantuan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMenuCategory(String title) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMenuItem({
|
||||||
|
required IconData icon,
|
||||||
|
IconData? activeIcon,
|
||||||
|
required String title,
|
||||||
|
bool isSelected = false,
|
||||||
|
String? badge,
|
||||||
|
required Function() onTap,
|
||||||
|
bool isLogout = false,
|
||||||
|
}) {
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: Duration(milliseconds: 200),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? AppTheme.primaryColor.withOpacity(0.1)
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
isSelected ? (activeIcon ?? icon) : icon,
|
||||||
|
color: isSelected
|
||||||
|
? AppTheme.primaryColor
|
||||||
|
: (isLogout ? Colors.red : null),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: isSelected
|
||||||
|
? AppTheme.primaryColor
|
||||||
|
: (isLogout ? Colors.red : null),
|
||||||
|
fontWeight: isSelected ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: badge != null
|
||||||
|
? Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 20,
|
||||||
|
minHeight: 20,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
badge,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class AppPages {
|
|||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.wargaDashboard,
|
name: Routes.wargaDashboard,
|
||||||
page: () => const WargaView(),
|
page: () => WargaView(),
|
||||||
binding: WargaBinding(),
|
binding: WargaBinding(),
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
@ -65,7 +65,7 @@ class AppPages {
|
|||||||
page: () {
|
page: () {
|
||||||
final controller = Get.find<WargaDashboardController>();
|
final controller = Get.find<WargaDashboardController>();
|
||||||
controller.activeTabIndex.value = 1;
|
controller.activeTabIndex.value = 1;
|
||||||
return const WargaView();
|
return WargaView();
|
||||||
},
|
},
|
||||||
binding: WargaBinding(),
|
binding: WargaBinding(),
|
||||||
),
|
),
|
||||||
@ -74,7 +74,7 @@ class AppPages {
|
|||||||
page: () {
|
page: () {
|
||||||
final controller = Get.find<WargaDashboardController>();
|
final controller = Get.find<WargaDashboardController>();
|
||||||
controller.activeTabIndex.value = 2;
|
controller.activeTabIndex.value = 2;
|
||||||
return const WargaView();
|
return WargaView();
|
||||||
},
|
},
|
||||||
binding: WargaBinding(),
|
binding: WargaBinding(),
|
||||||
),
|
),
|
||||||
|
@ -423,22 +423,39 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> getTotalBantuan() async {
|
// Metode untuk mendapatkan data penerima terbaru
|
||||||
|
Future<List<Map<String, dynamic>>?> getPenerimaTerbaru() async {
|
||||||
try {
|
try {
|
||||||
final response = await client.from('stok_bantuan').select('jumlah');
|
final response = await client
|
||||||
|
.from('warga')
|
||||||
|
.select('*')
|
||||||
|
.eq('status', 'AKTIF')
|
||||||
|
.order('created_at', ascending: false)
|
||||||
|
.limit(5);
|
||||||
|
|
||||||
double total = 0;
|
return response;
|
||||||
for (var item in response) {
|
|
||||||
total += (item['jumlah'] ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return total.toInt();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getting total bantuan: $e');
|
print('Error getting penerima terbaru: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Future<int?> getTotalBantuan() async {
|
||||||
|
// try {
|
||||||
|
// final response = await client.from('stok_bantuan').select('jumlah');
|
||||||
|
|
||||||
|
// double total = 0;
|
||||||
|
// for (var item in response) {
|
||||||
|
// total += (item['jumlah'] ?? 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return total.toInt();
|
||||||
|
// } catch (e) {
|
||||||
|
// print('Error getting total bantuan: $e');
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
Future<int?> getTotalPenyaluran() async {
|
Future<int?> getTotalPenyaluran() async {
|
||||||
try {
|
try {
|
||||||
final response = await client
|
final response = await client
|
||||||
@ -453,6 +470,18 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan total semua penyaluran (termasuk semua status)
|
||||||
|
Future<int?> getTotalSemuaPenyaluran() async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('penyaluran_bantuan').select('id');
|
||||||
|
|
||||||
|
return response.length;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting total semua penyaluran: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>?> getNotifikasiBelumDibaca(
|
Future<List<Map<String, dynamic>>?> getNotifikasiBelumDibaca(
|
||||||
String userId) async {
|
String userId) async {
|
||||||
try {
|
try {
|
||||||
@ -484,7 +513,13 @@ class SupabaseService extends GetxService {
|
|||||||
|
|
||||||
final response = await client
|
final response = await client
|
||||||
.from('penyaluran_bantuan')
|
.from('penyaluran_bantuan')
|
||||||
.select('*')
|
.select('''
|
||||||
|
*,
|
||||||
|
kategori_bantuan(*),
|
||||||
|
lokasi_penyaluran:lokasi_penyaluran_id(
|
||||||
|
id, nama, alamat_lengkap
|
||||||
|
)
|
||||||
|
''')
|
||||||
.gte('tanggal_penyaluran', todayUtc)
|
.gte('tanggal_penyaluran', todayUtc)
|
||||||
.lt('tanggal_penyaluran', tomorrowUtc)
|
.lt('tanggal_penyaluran', tomorrowUtc)
|
||||||
.inFilter('status', ['AKTIF', 'DIJADWALKAN']);
|
.inFilter('status', ['AKTIF', 'DIJADWALKAN']);
|
||||||
@ -713,6 +748,21 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan total penitipan terverifikasi
|
||||||
|
Future<int?> getTotalPenitipanTerverifikasi() async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('penitipan_bantuan')
|
||||||
|
.select('id')
|
||||||
|
.eq('status', 'TERVERIFIKASI');
|
||||||
|
|
||||||
|
return response.length;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting total penitipan terverifikasi: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Metode untuk mengambil data penitipan bantuan dengan status TERVERIFIKASI
|
// Metode untuk mengambil data penitipan bantuan dengan status TERVERIFIKASI
|
||||||
Future<List<Map<String, dynamic>>?> getPenitipanBantuanTerverifikasi() async {
|
Future<List<Map<String, dynamic>>?> getPenitipanBantuanTerverifikasi() async {
|
||||||
try {
|
try {
|
||||||
@ -1109,11 +1159,31 @@ class SupabaseService extends GetxService {
|
|||||||
Future<List<Map<String, dynamic>>?> getPenyaluranBantuanByWargaId(
|
Future<List<Map<String, dynamic>>?> getPenyaluranBantuanByWargaId(
|
||||||
String wargaId) async {
|
String wargaId) async {
|
||||||
try {
|
try {
|
||||||
final response = await client
|
// Pertama, cari warga berdasarkan NIP untuk mendapatkan UUID-nya
|
||||||
.from('penyaluran_bantuan')
|
final wargaResponse = await client
|
||||||
.select('*, stok_bantuan:stok_bantuan_id(*)')
|
.from('warga')
|
||||||
.eq('penerima_id', wargaId)
|
.select('id')
|
||||||
.order('tanggal_penyaluran', ascending: false);
|
.eq('id', wargaId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (wargaResponse == null) {
|
||||||
|
print('Warning: Warga dengan NIP $wargaId tidak ditemukan');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final wargaUuid = wargaResponse['id'];
|
||||||
|
|
||||||
|
// Kemudian gunakan UUID untuk mencari penyaluran bantuan
|
||||||
|
final response = await client.from('penerima_penyaluran').select('''
|
||||||
|
*,
|
||||||
|
penyaluran_bantuan:penyaluran_bantuan_id(
|
||||||
|
*,
|
||||||
|
kategori_bantuan(*),
|
||||||
|
lokasi_penyaluran:lokasi_penyaluran_id(
|
||||||
|
id, nama, alamat_lengkap
|
||||||
|
)
|
||||||
|
)
|
||||||
|
''').eq('warga_id', wargaUuid).order('created_at', ascending: false);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1339,6 +1409,53 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memperbarui profil donatur
|
||||||
|
Future<void> updateDonaturProfile({
|
||||||
|
required String userId,
|
||||||
|
required String nama,
|
||||||
|
required String email,
|
||||||
|
String? noHp,
|
||||||
|
String? fotoProfil,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
// Buat map untuk update data
|
||||||
|
final Map<String, dynamic> updateData = {
|
||||||
|
'nama': nama,
|
||||||
|
'nama_lengkap': nama, // Untuk konsistensi dengan field nama_lengkap
|
||||||
|
'no_hp': noHp,
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tambahkan foto profil jika ada
|
||||||
|
if (fotoProfil != null) {
|
||||||
|
// Jika string kosong, set null untuk menghapus foto profil
|
||||||
|
if (fotoProfil.isEmpty) {
|
||||||
|
updateData['foto_profil'] = null;
|
||||||
|
} else {
|
||||||
|
updateData['foto_profil'] = fotoProfil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update data donatur
|
||||||
|
await client.from('donatur').update(updateData).eq('id', userId);
|
||||||
|
|
||||||
|
// Update email di auth.users jika berubah
|
||||||
|
if (email != client.auth.currentUser?.email) {
|
||||||
|
// Gunakan metode updateUserEmail
|
||||||
|
await client.auth.updateUser(UserAttributes(
|
||||||
|
email: email,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hapus cache user profile
|
||||||
|
_cachedUserProfile = null;
|
||||||
|
print('Cache profil user dihapus setelah update donatur');
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating donatur profile: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Metode untuk membuat profil petugas desa
|
// Metode untuk membuat profil petugas desa
|
||||||
Future<void> createPetugasDesaProfile({
|
Future<void> createPetugasDesaProfile({
|
||||||
required String nama_lengkap,
|
required String nama_lengkap,
|
||||||
@ -1647,8 +1764,13 @@ class SupabaseService extends GetxService {
|
|||||||
|
|
||||||
// Tambahkan foto profil jika ada
|
// Tambahkan foto profil jika ada
|
||||||
if (fotoProfil != null) {
|
if (fotoProfil != null) {
|
||||||
|
// Jika string kosong, set null untuk menghapus foto profil
|
||||||
|
if (fotoProfil.isEmpty) {
|
||||||
|
updateData['foto_profil'] = null;
|
||||||
|
} else {
|
||||||
updateData['foto_profil'] = fotoProfil;
|
updateData['foto_profil'] = fotoProfil;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update data warga
|
// Update data warga
|
||||||
await client.from('warga').update(updateData).eq('id', userId);
|
await client.from('warga').update(updateData).eq('id', userId);
|
||||||
@ -1660,50 +1782,16 @@ class SupabaseService extends GetxService {
|
|||||||
email: email,
|
email: email,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hapus cache user profile
|
||||||
|
_cachedUserProfile = null;
|
||||||
|
print('Cache profil user dihapus setelah update warga');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error updating warga profile: $e');
|
print('Error updating warga profile: $e');
|
||||||
throw e.toString();
|
throw e.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui profil donatur
|
|
||||||
Future<void> updateDonaturProfile({
|
|
||||||
required String userId,
|
|
||||||
required String nama,
|
|
||||||
required String email,
|
|
||||||
String? noHp,
|
|
||||||
String? fotoProfil,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// Buat map untuk update data
|
|
||||||
final Map<String, dynamic> updateData = {
|
|
||||||
'nama': nama,
|
|
||||||
'nama_lengkap': nama, // Untuk konsistensi dengan field nama_lengkap
|
|
||||||
'no_hp': noHp,
|
|
||||||
'updated_at': DateTime.now().toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tambahkan foto profil jika ada
|
|
||||||
if (fotoProfil != null) {
|
|
||||||
updateData['foto_profil'] = fotoProfil;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update data donatur
|
|
||||||
await client.from('donatur').update(updateData).eq('id', userId);
|
|
||||||
|
|
||||||
// Update email di auth.users jika berubah
|
|
||||||
if (email != client.auth.currentUser?.email) {
|
|
||||||
// Gunakan metode updateUserEmail
|
|
||||||
await client.auth.updateUser(UserAttributes(
|
|
||||||
email: email,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error updating donatur profile: $e');
|
|
||||||
throw e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk memperbarui profil petugas desa
|
// Metode untuk memperbarui profil petugas desa
|
||||||
Future<void> updatePetugasDesaProfile({
|
Future<void> updatePetugasDesaProfile({
|
||||||
required String userId,
|
required String userId,
|
||||||
@ -1722,8 +1810,13 @@ class SupabaseService extends GetxService {
|
|||||||
|
|
||||||
// Tambahkan foto profil jika ada
|
// Tambahkan foto profil jika ada
|
||||||
if (fotoProfil != null) {
|
if (fotoProfil != null) {
|
||||||
|
// Jika string kosong, set null untuk menghapus foto profil
|
||||||
|
if (fotoProfil.isEmpty) {
|
||||||
|
updateData['foto_profil'] = null;
|
||||||
|
} else {
|
||||||
updateData['foto_profil'] = fotoProfil;
|
updateData['foto_profil'] = fotoProfil;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update data petugas desa
|
// Update data petugas desa
|
||||||
await client.from('petugas_desa').update(updateData).eq('id', userId);
|
await client.from('petugas_desa').update(updateData).eq('id', userId);
|
||||||
@ -1735,6 +1828,10 @@ class SupabaseService extends GetxService {
|
|||||||
email: email,
|
email: email,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hapus cache user profile
|
||||||
|
_cachedUserProfile = null;
|
||||||
|
print('Cache profil user dihapus setelah update petugas desa');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error updating petugas desa profile: $e');
|
print('Error updating petugas desa profile: $e');
|
||||||
throw e.toString();
|
throw e.toString();
|
||||||
|
@ -105,7 +105,7 @@ class BantuanCard extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
StatusBadge(
|
StatusBadge(
|
||||||
status: item.statusPenerimaan ?? 'MENUNGGU',
|
status: item.statusPenerimaan ?? 'BELUMMENERIMA',
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
@ -177,7 +177,7 @@ class BantuanCard extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
StatusBadge(status: item.statusPenyaluran ?? ""),
|
StatusBadge(status: item.statusPenyaluran ?? ""),
|
||||||
StatusBadge(
|
StatusBadge(
|
||||||
status: item.statusPenerimaan ?? 'MENUNGGU',
|
status: item.statusPenerimaan ?? 'BELUMMENERIMA',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -693,6 +693,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.11.3"
|
version: "3.11.3"
|
||||||
|
percent_indicator:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: percent_indicator
|
||||||
|
sha256: "0d77d5c6fa9b7f60202cedf748b568ba9ba38d3f30405d6ceae4da76f5185462"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.4"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -90,6 +90,7 @@ dependencies:
|
|||||||
path_provider: ^2.1.2
|
path_provider: ^2.1.2
|
||||||
# Package untuk membuka file
|
# Package untuk membuka file
|
||||||
open_file: ^3.3.2
|
open_file: ^3.3.2
|
||||||
|
percent_indicator: ^4.2.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user