Perbarui beberapa file konfigurasi fingerprint untuk arsitektur arm64-v8a, armeabi-v7a, x86, dan x86_64. Modifikasi tampilan dan controller di modul donatur dan petugas desa untuk meningkatkan pengalaman pengguna, termasuk penggantian logika pengambilan data dan penyesuaian tampilan. Hapus kode yang tidak digunakan dan tambahkan fungsionalitas baru untuk mendukung pengelolaan data yang lebih baik.
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>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
|
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
|
||||||
|
|
||||||
}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt <08><>Ӊ<EFBFBD>2 <20><><EFBFBD><EFBFBD><EFBFBD>2~
|
}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt <08><><EFBFBD><EFBFBD><EFBFBD>2 <20><><EFBFBD><EFBFBD><EFBFBD>2~
|
||||||
|
|
|
|
||||||
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json <08><>Ӊ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
|
||||||
<EFBFBD>
|
<EFBFBD>
|
||||||
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json <08><>Ӊ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p
|
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p
|
||||||
n
|
n
|
||||||
lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja <08><>Ӊ<EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t
|
lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t
|
||||||
r
|
r
|
||||||
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt <08><>Ӊ<EFBFBD>2y
|
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt <08><><EFBFBD><EFBFBD><EFBFBD>2y
|
||||||
w
|
w
|
||||||
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt <08><>Ӊ<EFBFBD>2
|
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt <08><><EFBFBD><EFBFBD><EFBFBD>2
|
||||||
K <20><><EFBFBD><EFBFBD><EFBFBD>2z
|
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 ˝Ӊ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
|
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint <EFBFBD><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 ̝Ӊ<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 <EFBFBD><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 ̝Ӊ<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 <EFBFBD><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 ̝Ӊ<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 <EFBFBD><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 ̝Ӊ<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 <EFBFBD><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 ΝӉ<EFBFBD>2{
|
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt <EFBFBD><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 ΝӉ<EFBFBD>2
|
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt <EFBFBD><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 <08><>Ӊ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{
|
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{
|
||||||
y
|
y
|
||||||
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt <08><>Ӊ<EFBFBD>2 <20><><EFBFBD><EFBFBD><EFBFBD>2x
|
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt <08><><EFBFBD><EFBFBD><EFBFBD>2 <20><><EFBFBD><EFBFBD><EFBFBD>2x
|
||||||
v
|
v
|
||||||
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json <08><>Ӊ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2}
|
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2}
|
||||||
{
|
{
|
||||||
yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json <08><>Ӊ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j
|
yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j
|
||||||
h
|
h
|
||||||
fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja <08><>Ӊ<EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n
|
fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n
|
||||||
l
|
l
|
||||||
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt <08><>Ӊ<EFBFBD>2s
|
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt <08><><EFBFBD><EFBFBD><EFBFBD>2s
|
||||||
q
|
q
|
||||||
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt <08><>Ӊ<EFBFBD>2
|
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt <08><><EFBFBD><EFBFBD><EFBFBD>2
|
||||||
K <20><><EFBFBD><EFBFBD><EFBFBD>2t
|
K <20><><EFBFBD><EFBFBD><EFBFBD>2t
|
||||||
r
|
r
|
||||||
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json <08><><EFBFBD><EFBFBD><EFBFBD>2 x
|
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json <08><><EFBFBD><EFBFBD><EFBFBD>2 x
|
||||||
v
|
v
|
||||||
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin <08><><EFBFBD><EFBFBD><EFBFBD>2
|
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin <08><><EFBFBD><EFBFBD><EFBFBD>2
|
||||||
~
|
~
|
||||||
|
|
|
|
||||||
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2q
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2q
|
||||||
o
|
o
|
||||||
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json <08><><EFBFBD><EFBFBD><EFBFBD>2
|
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json <08><><EFBFBD><EFBFBD><EFBFBD>2
|
||||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2v
|
( <20><><EFBFBD><EFBFBD><EFBFBD>2v
|
@ -2,27 +2,27 @@ C/C++ Structured LogO
|
|||||||
M
|
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>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2~
|
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint <EFBFBD><EFBFBD><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 ȠӉ<EFBFBD>2 <20><><EFBFBD><EFBFBD><EFBFBD>2{
|
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt <EFBFBD><EFBFBD><EFBFBD><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 ȠӉ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
|
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json <EFBFBD><EFBFBD><EFBFBD><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 ϠӉ<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2m
|
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json <EFBFBD><EFBFBD><EFBFBD><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 ϠӉ<EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2q
|
iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja <EFBFBD><EFBFBD><EFBFBD><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 ϠӉ<EFBFBD>2v
|
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt <EFBFBD><EFBFBD><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 ڠӉ<EFBFBD>2
|
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt <EFBFBD><EFBFBD><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
|
@ -11,9 +11,6 @@ import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart
|
|||||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
||||||
|
|
||||||
class DonaturDashboardController extends GetxController {
|
class DonaturDashboardController extends GetxController {
|
||||||
final AuthController _authController = Get.find<AuthController>();
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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/data/models/skema_bantuan_model.dart';
|
import 'package:penyaluran_app/app/data/models/skema_bantuan_model.dart';
|
||||||
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
|
import 'package:penyaluran_app/app/modules/donatur/controllers/donatur_dashboard_controller.dart';
|
||||||
import 'package:penyaluran_app/app/widgets/section_header.dart';
|
import 'package:penyaluran_app/app/widgets/section_header.dart';
|
||||||
|
@ -8,11 +8,16 @@ import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
|||||||
|
|
||||||
class CalendarViewWidget extends StatelessWidget {
|
class CalendarViewWidget extends StatelessWidget {
|
||||||
final JadwalPenyaluranController controller;
|
final JadwalPenyaluranController controller;
|
||||||
|
final CalendarController _calendarController = CalendarController();
|
||||||
|
|
||||||
const CalendarViewWidget({
|
CalendarViewWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
});
|
}) {
|
||||||
|
// Mengatur controller kalender untuk selalu memilih hari ini saat inisialisasi
|
||||||
|
_calendarController.selectedDate = DateTime.now();
|
||||||
|
_calendarController.displayDate = DateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -49,6 +54,9 @@ class CalendarViewWidget extends StatelessWidget {
|
|||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
return SfCalendar(
|
return SfCalendar(
|
||||||
view: CalendarView.month,
|
view: CalendarView.month,
|
||||||
|
controller: _calendarController,
|
||||||
|
initialSelectedDate: DateTime.now(),
|
||||||
|
initialDisplayDate: DateTime.now(),
|
||||||
dataSource: _getCalendarDataSource(),
|
dataSource: _getCalendarDataSource(),
|
||||||
timeZone: 'Asia/Jakarta',
|
timeZone: 'Asia/Jakarta',
|
||||||
monthViewSettings: MonthViewSettings(
|
monthViewSettings: MonthViewSettings(
|
||||||
@ -246,7 +254,7 @@ class CalendarViewWidget extends StatelessWidget {
|
|||||||
List<Appointment> appointments = [];
|
List<Appointment> appointments = [];
|
||||||
|
|
||||||
List<PenyaluranBantuanModel> allJadwal = [
|
List<PenyaluranBantuanModel> allJadwal = [
|
||||||
...controller.jadwalHariIni,
|
...controller.jadwalAktif,
|
||||||
...controller.jadwalMendatang,
|
...controller.jadwalMendatang,
|
||||||
...controller.jadwalTerlaksana,
|
...controller.jadwalTerlaksana,
|
||||||
];
|
];
|
||||||
@ -556,7 +564,7 @@ class CalendarViewWidget extends StatelessWidget {
|
|||||||
|
|
||||||
// Cari jadwal dengan ID yang sesuai
|
// Cari jadwal dengan ID yang sesuai
|
||||||
for (var jadwal in [
|
for (var jadwal in [
|
||||||
...controller.jadwalHariIni,
|
...controller.jadwalAktif,
|
||||||
...controller.jadwalMendatang,
|
...controller.jadwalMendatang,
|
||||||
...controller.jadwalTerlaksana
|
...controller.jadwalTerlaksana
|
||||||
]) {
|
]) {
|
||||||
|
@ -165,9 +165,10 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
|
|
||||||
List<PenyaluranBantuanModel> _getCurrentJadwalList() {
|
List<PenyaluranBantuanModel> _getCurrentJadwalList() {
|
||||||
switch (title) {
|
switch (title) {
|
||||||
case 'Hari Ini':
|
case 'Penyaluran Aktif':
|
||||||
return controller.jadwalHariIni.toList();
|
return controller.jadwalAktif.toList();
|
||||||
case 'Mendatang':
|
|
||||||
|
case '7 Hari Mendatang':
|
||||||
return controller.jadwalMendatang.toList();
|
return controller.jadwalMendatang.toList();
|
||||||
case 'Terlaksana':
|
case 'Terlaksana':
|
||||||
return controller.jadwalTerlaksana.toList();
|
return controller.jadwalTerlaksana.toList();
|
||||||
|
@ -73,17 +73,23 @@ class CounterService extends GetxService {
|
|||||||
void updatePengaduanCounter(int diproses) {
|
void updatePengaduanCounter(int diproses) {
|
||||||
jumlahDiproses.value = diproses;
|
jumlahDiproses.value = diproses;
|
||||||
_storage.write(_keyDiproses, diproses);
|
_storage.write(_keyDiproses, diproses);
|
||||||
|
|
||||||
|
print('Counter pengaduan updated and saved - Diproses: $diproses');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui counter notifikasi
|
// Metode untuk memperbarui counter notifikasi
|
||||||
void updateNotifikasiCounter(int belumDibaca) {
|
void updateNotifikasiCounter(int belumDibaca) {
|
||||||
jumlahNotifikasiBelumDibaca.value = belumDibaca;
|
jumlahNotifikasiBelumDibaca.value = belumDibaca;
|
||||||
_storage.write(_keyNotifikasi, belumDibaca);
|
_storage.write(_keyNotifikasi, belumDibaca);
|
||||||
|
|
||||||
|
print('Counter notifikasi updated and saved - Belum Dibaca: $belumDibaca');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui counter jadwal
|
// Metode untuk memperbarui counter jadwal
|
||||||
void updateJadwalCounter(int hariIni) {
|
void updateJadwalCounter(int hariIni) {
|
||||||
jumlahJadwalHariIni.value = hariIni;
|
jumlahJadwalHariIni.value = hariIni;
|
||||||
_storage.write(_keyJadwal, hariIni);
|
_storage.write(_keyJadwal, hariIni);
|
||||||
|
|
||||||
|
print('Counter jadwal updated and saved - Hari Ini: $hariIni');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,6 +204,27 @@ class DetailPenyaluranController extends GetxController {
|
|||||||
.update(updateData)
|
.update(updateData)
|
||||||
.eq('id', penerima.id!);
|
.eq('id', penerima.id!);
|
||||||
|
|
||||||
|
// Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah)
|
||||||
|
final penerimaData = await _supabaseService.client
|
||||||
|
.from('penerima_penyaluran')
|
||||||
|
.select('penyaluran_bantuan_id, stok_bantuan_id, jumlah_bantuan')
|
||||||
|
.eq('id', penerima.id!)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (penerimaData != null) {
|
||||||
|
final String stokBantuanId = penerimaData['stok_bantuan_id'];
|
||||||
|
final double jumlah = penerimaData['jumlah_bantuan'] is int
|
||||||
|
? penerimaData['jumlah_bantuan'].toDouble()
|
||||||
|
: penerimaData['jumlah_bantuan'];
|
||||||
|
|
||||||
|
// Kurangi stok dan catat riwayat
|
||||||
|
final petugasId = _supabaseService.client.auth.currentUser?.id;
|
||||||
|
if (petugasId != null) {
|
||||||
|
await _supabaseService.kurangiStokDariPenyaluran(
|
||||||
|
penerima.id!, stokBantuanId, jumlah, petugasId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh data setelah konfirmasi berhasil
|
// Refresh data setelah konfirmasi berhasil
|
||||||
await refreshData();
|
await refreshData();
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
final RxInt selectedCategoryIndex = 0.obs;
|
final RxInt selectedCategoryIndex = 0.obs;
|
||||||
|
|
||||||
// Data untuk jadwal
|
// Data untuk jadwal
|
||||||
final RxList<PenyaluranBantuanModel> jadwalHariIni =
|
final RxList<PenyaluranBantuanModel> jadwalAktif =
|
||||||
<PenyaluranBantuanModel>[].obs;
|
<PenyaluranBantuanModel>[].obs;
|
||||||
final RxList<PenyaluranBantuanModel> jadwalMendatang =
|
final RxList<PenyaluranBantuanModel> jadwalMendatang =
|
||||||
<PenyaluranBantuanModel>[].obs;
|
<PenyaluranBantuanModel>[].obs;
|
||||||
@ -97,7 +97,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
List<PenyaluranBantuanModel> jadwalToUpdate = [];
|
List<PenyaluranBantuanModel> jadwalToUpdate = [];
|
||||||
List<PenyaluranBantuanModel> jadwalTerlewat = [];
|
List<PenyaluranBantuanModel> jadwalTerlewat = [];
|
||||||
|
|
||||||
for (var jadwal in jadwalHariIni) {
|
for (var jadwal in jadwalAktif) {
|
||||||
if (jadwal.tanggalPenyaluran != null) {
|
if (jadwal.tanggalPenyaluran != null) {
|
||||||
final jadwalDateTime =
|
final jadwalDateTime =
|
||||||
DateTimeHelper.toLocalDateTime(jadwal.tanggalPenyaluran!);
|
DateTimeHelper.toLocalDateTime(jadwal.tanggalPenyaluran!);
|
||||||
@ -175,9 +175,9 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
// Mengambil data jadwal hari ini
|
// Mengambil data jadwal hari ini
|
||||||
final jadwalHariIniData = await _supabaseService.getJadwalHariIni();
|
final jadwalAktifData = await _supabaseService.getJadwalAktif();
|
||||||
if (jadwalHariIniData != null) {
|
if (jadwalAktifData != null) {
|
||||||
jadwalHariIni.value = jadwalHariIniData
|
jadwalAktif.value = jadwalAktifData
|
||||||
.map((data) => PenyaluranBantuanModel.fromJson(data))
|
.map((data) => PenyaluranBantuanModel.fromJson(data))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
@ -250,29 +250,29 @@ class PelaksanaanPenyaluranController extends GetxController {
|
|||||||
filteredPenerima.value = filtered;
|
filteredPenerima.value = filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui status penerimaan bantuan
|
// // Metode untuk memperbarui status penerimaan bantuan
|
||||||
Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
// Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
||||||
{DateTime? tanggalPenerimaan,
|
// {DateTime? tanggalPenerimaan,
|
||||||
String? buktiPenerimaan,
|
// String? buktiPenerimaan,
|
||||||
String? keterangan}) async {
|
// String? keterangan}) async {
|
||||||
try {
|
// try {
|
||||||
final result = await supabaseService.updateStatusPenerimaan(
|
// final result = await supabaseService.updateStatusPenerimaan(
|
||||||
penerimaId, status,
|
// penerimaId, status,
|
||||||
tanggalPenerimaan: tanggalPenerimaan,
|
// tanggalPenerimaan: tanggalPenerimaan,
|
||||||
buktiPenerimaan: buktiPenerimaan,
|
// buktiPenerimaan: buktiPenerimaan,
|
||||||
keterangan: keterangan);
|
// keterangan: keterangan);
|
||||||
|
|
||||||
// Jika berhasil, perbarui data lokal
|
// // Jika berhasil, perbarui data lokal
|
||||||
if (result) {
|
// if (result) {
|
||||||
await loadPenerimaPenyaluran(activePenyaluranId.value);
|
// await loadPenerimaPenyaluran(activePenyaluranId.value);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return result;
|
// return result;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print('Error updating status penerimaan: $e');
|
// print('Error updating status penerimaan: $e');
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Metode untuk menyelesaikan jadwal penyaluran
|
// Metode untuk menyelesaikan jadwal penyaluran
|
||||||
Future<void> completeJadwal(String jadwalId) async {
|
Future<void> completeJadwal(String jadwalId) async {
|
||||||
|
@ -309,10 +309,10 @@ class PetugasDesaController extends GetxController {
|
|||||||
// Metode untuk memuat data jadwal
|
// Metode untuk memuat data jadwal
|
||||||
Future<void> loadJadwalData() async {
|
Future<void> loadJadwalData() async {
|
||||||
try {
|
try {
|
||||||
final jadwalHariIniData = await _supabaseService.getJadwalHariIni();
|
final jadwalAktifData = await _supabaseService.getJadwalAktif();
|
||||||
if (jadwalHariIniData != null) {
|
if (jadwalAktifData != null) {
|
||||||
jadwalHariIni.value = jadwalHariIniData;
|
jadwalHariIni.value = jadwalAktifData;
|
||||||
_counterService.updateJadwalCounter(jadwalHariIniData.length);
|
_counterService.updateJadwalCounter(jadwalAktifData.length);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading jadwal data: $e');
|
print('Error loading jadwal data: $e');
|
||||||
@ -360,7 +360,7 @@ class PetugasDesaController extends GetxController {
|
|||||||
|
|
||||||
// Hitung jumlah pengaduan dengan status DIPROSES
|
// Hitung jumlah pengaduan dengan status DIPROSES
|
||||||
for (var item in pengaduanData) {
|
for (var item in pengaduanData) {
|
||||||
if (item['status'] == 'DIPROSES') {
|
if (item['status'] == 'MENUNGGU') {
|
||||||
diproses++;
|
diproses++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -609,22 +609,22 @@ class PetugasDesaController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui status penerimaan bantuan
|
// Metode untuk memperbarui status penerimaan bantuan
|
||||||
Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
// Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
||||||
{DateTime? tanggalPenerimaan,
|
// {DateTime? tanggalPenerimaan,
|
||||||
String? buktiPenerimaan,
|
// String? buktiPenerimaan,
|
||||||
String? keterangan}) async {
|
// String? keterangan}) async {
|
||||||
try {
|
// try {
|
||||||
final result = await _supabaseService.updateStatusPenerimaan(
|
// final result = await _supabaseService.updateStatusPenerimaan(
|
||||||
penerimaId, status,
|
// penerimaId, status,
|
||||||
tanggalPenerimaan: tanggalPenerimaan,
|
// tanggalPenerimaan: tanggalPenerimaan,
|
||||||
buktiPenerimaan: buktiPenerimaan,
|
// buktiPenerimaan: buktiPenerimaan,
|
||||||
keterangan: keterangan);
|
// keterangan: keterangan);
|
||||||
return result;
|
// return result;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print('Error updating status penerimaan: $e');
|
// print('Error updating status penerimaan: $e');
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Metode untuk menyelesaikan jadwal penyaluran
|
// Metode untuk menyelesaikan jadwal penyaluran
|
||||||
Future<void> completeJadwal(String jadwalId) async {
|
Future<void> completeJadwal(String jadwalId) async {
|
||||||
|
@ -4,10 +4,12 @@ import 'package:penyaluran_app/app/data/models/user_model.dart';
|
|||||||
import 'package:penyaluran_app/app/data/models/notifikasi_model.dart';
|
import 'package:penyaluran_app/app/data/models/notifikasi_model.dart';
|
||||||
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/counter_service.dart';
|
||||||
|
|
||||||
class PetugasDesaDashboardController extends GetxController {
|
class PetugasDesaDashboardController extends GetxController {
|
||||||
final AuthController _authController = Get.find<AuthController>();
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
final SupabaseService _supabaseService = SupabaseService.to;
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
late final CounterService _counterService;
|
||||||
|
|
||||||
final RxBool isLoading = false.obs;
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
@ -22,6 +24,12 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
final RxInt totalPenitipanTerverifikasi = 0.obs;
|
final RxInt totalPenitipanTerverifikasi = 0.obs;
|
||||||
final RxDouble progressPenyaluran = 0.0.obs;
|
final RxDouble progressPenyaluran = 0.0.obs;
|
||||||
|
|
||||||
|
// Data untuk status penyaluran
|
||||||
|
final RxInt penyaluranDijadwalkan = 0.obs;
|
||||||
|
final RxInt penyaluranAktif = 0.obs;
|
||||||
|
final RxInt penyaluranBatal = 0.obs;
|
||||||
|
final RxInt penyaluranTerlaksana = 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;
|
||||||
@ -45,13 +53,24 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
userProfile['desa']?['nama'] ??
|
userProfile['desa']?['nama'] ??
|
||||||
(userProfile['desa_id'] != null ? 'Desa' : 'Desa');
|
(userProfile['desa_id'] != null ? 'Desa' : 'Desa');
|
||||||
|
|
||||||
|
// Getter untuk counter dari CounterService
|
||||||
|
RxInt get jumlahMenunggu => _counterService.jumlahMenunggu;
|
||||||
|
RxInt get jumlahDiproses => _counterService.jumlahDiproses;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
|
// Inisialisasi CounterService jika belum ada
|
||||||
|
if (!Get.isRegistered<CounterService>()) {
|
||||||
|
Get.put(CounterService(), permanent: true);
|
||||||
|
}
|
||||||
|
_counterService = Get.find<CounterService>();
|
||||||
|
|
||||||
loadUserProfile();
|
loadUserProfile();
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
loadNotifikasiData();
|
loadNotifikasiData();
|
||||||
loadJadwalHariIni();
|
loadJadwalAktif();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -97,6 +116,15 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
await _supabaseService.getTotalSemuaPenyaluran();
|
await _supabaseService.getTotalSemuaPenyaluran();
|
||||||
totalSemuaPenyaluran.value = semuaPenyaluranData ?? 0;
|
totalSemuaPenyaluran.value = semuaPenyaluranData ?? 0;
|
||||||
|
|
||||||
|
// Mengambil data status penyaluran
|
||||||
|
final statusPenyaluranData = await _supabaseService.getStatusPenyaluran();
|
||||||
|
if (statusPenyaluranData != null) {
|
||||||
|
penyaluranDijadwalkan.value = statusPenyaluranData['dijadwalkan'] ?? 0;
|
||||||
|
penyaluranAktif.value = statusPenyaluranData['aktif'] ?? 0;
|
||||||
|
penyaluranBatal.value = statusPenyaluranData['batal'] ?? 0;
|
||||||
|
penyaluranTerlaksana.value = statusPenyaluranData['terlaksana'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Menghitung progress penyaluran (persentase penyaluran yang terlaksana dari total semua penyaluran)
|
// Menghitung progress penyaluran (persentase penyaluran yang terlaksana dari total semua penyaluran)
|
||||||
if (totalSemuaPenyaluran.value > 0) {
|
if (totalSemuaPenyaluran.value > 0) {
|
||||||
progressPenyaluran.value =
|
progressPenyaluran.value =
|
||||||
@ -127,9 +155,9 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadJadwalHariIni() async {
|
Future<void> loadJadwalAktif() async {
|
||||||
try {
|
try {
|
||||||
final jadwalData = await _supabaseService.getJadwalHariIni();
|
final jadwalData = await _supabaseService.getJadwalAktif();
|
||||||
if (jadwalData != null) {
|
if (jadwalData != null) {
|
||||||
jadwalHariIni.value = jadwalData;
|
jadwalHariIni.value = jadwalData;
|
||||||
}
|
}
|
||||||
@ -145,7 +173,7 @@ class PetugasDesaDashboardController extends GetxController {
|
|||||||
loadUserProfile(),
|
loadUserProfile(),
|
||||||
loadDashboardData(),
|
loadDashboardData(),
|
||||||
loadNotifikasiData(),
|
loadNotifikasiData(),
|
||||||
loadJadwalHariIni(),
|
loadJadwalAktif(),
|
||||||
]);
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error refreshing data: $e');
|
print('Error refreshing data: $e');
|
||||||
|
@ -302,4 +302,26 @@ class RiwayatStokController extends GetxController {
|
|||||||
alasan.value = '';
|
alasan.value = '';
|
||||||
fotoBukti.value = null;
|
fotoBukti.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan detail referensi berdasarkan id dan sumber
|
||||||
|
Future<Map<String, dynamic>?> getReferensiDetail({
|
||||||
|
required String idReferensi,
|
||||||
|
required String sumber,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
Map<String, dynamic>? data;
|
||||||
|
|
||||||
|
// Berdasarkan sumber, ambil data dari tabel yang sesuai
|
||||||
|
if (sumber == 'penitipan') {
|
||||||
|
data = await _supabaseService.getPenitipanById(idReferensi);
|
||||||
|
} else if (sumber == 'penerimaan') {
|
||||||
|
data = await _supabaseService.getPenerimaanById(idReferensi);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting referensi detail: $e');
|
||||||
|
throw Exception('Gagal mendapatkan data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
FutureBuilder<List<Map<String, dynamic>>?>(
|
FutureBuilder<List<Map<String, dynamic>>?>(
|
||||||
future: SupabaseService.to.getJadwalHariIni(),
|
future: SupabaseService.to.getJadwalAktif(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@ -153,11 +153,16 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProgressPenyaluran() {
|
Widget _buildProgressPenyaluran() {
|
||||||
// Menghitung nilai untuk progress
|
// Menghitung nilai untuk progress berdasarkan status
|
||||||
final terlaksana = controller.totalPenyaluran.value;
|
final terlaksana = controller.penyaluranTerlaksana.value;
|
||||||
final total = controller.totalSemuaPenyaluran.value;
|
final batal = controller.penyaluranBatal.value;
|
||||||
final progressValue = total > 0 ? terlaksana / total : 0.0;
|
final dijadwalkan = controller.penyaluranDijadwalkan.value;
|
||||||
final belumTerlaksana = total - terlaksana;
|
final aktif = controller.penyaluranAktif.value;
|
||||||
|
|
||||||
|
final total = terlaksana + batal + dijadwalkan + aktif;
|
||||||
|
final progressValue = total > 0 ? (terlaksana + batal) / total : 0.0;
|
||||||
|
final belumTerlaksana = dijadwalkan +
|
||||||
|
aktif; // Yang belum terlaksana adalah yang dijadwalkan dan aktif
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@ -214,6 +219,12 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
|||||||
Colors.white.withOpacity(0.7),
|
Colors.white.withOpacity(0.7),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
_buildProgressDetailItem(
|
||||||
|
'Dibatalkan',
|
||||||
|
'$batal',
|
||||||
|
Colors.white.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
_buildProgressDetailItem(
|
_buildProgressDetailItem(
|
||||||
'Total Penyaluran',
|
'Total Penyaluran',
|
||||||
'$total',
|
'$total',
|
||||||
@ -270,18 +281,22 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: StatisticCard(
|
child: StatisticCard(
|
||||||
title: 'Penitipan',
|
title: 'Penitipan',
|
||||||
count: controller.jumlahNotifikasiBelumDibaca.toString(),
|
count: controller.jumlahMenunggu.value.toString(),
|
||||||
subtitle: 'Perlu Konfirmasi',
|
subtitle: 'Perlu Konfirmasi',
|
||||||
height: 120,
|
height: 120,
|
||||||
icon: Icons.inbox,
|
icon: Icons.inbox,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.orange, Colors.deepOrange],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: StatisticCard(
|
child: StatisticCard(
|
||||||
title: 'Pengaduan',
|
title: 'Pengaduan',
|
||||||
count:
|
count: controller.jumlahDiproses.value.toString(),
|
||||||
'${controller.totalPenerima.value > 0 ? controller.totalPenerima.value ~/ 10 : 0}',
|
|
||||||
subtitle: 'Perlu Tindakan',
|
subtitle: 'Perlu Tindakan',
|
||||||
height: 120,
|
height: 120,
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
|
@ -698,10 +698,7 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
|
|||||||
try {
|
try {
|
||||||
imageUrl =
|
imageUrl =
|
||||||
await controller.uploadBuktiPenerimaan(_buktiPenerimaan!.path);
|
await controller.uploadBuktiPenerimaan(_buktiPenerimaan!.path);
|
||||||
print('Berhasil upload bukti penerimaan: $imageUrl');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Jika upload bukti penerimaan gagal, tampilkan pesan dan hentikan proses
|
|
||||||
print('Error upload bukti penerimaan: $e');
|
|
||||||
throw Exception('Gagal mengupload bukti penerimaan: $e');
|
throw Exception('Gagal mengupload bukti penerimaan: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,46 +708,31 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
|
|||||||
signatureFile = File('${tempDir.path}/signature.png');
|
signatureFile = File('${tempDir.path}/signature.png');
|
||||||
await signatureFile.writeAsBytes(_signatureImage!);
|
await signatureFile.writeAsBytes(_signatureImage!);
|
||||||
|
|
||||||
print('Signature file path: ${signatureFile.path}');
|
|
||||||
print('Signature file exists: ${signatureFile.existsSync()}');
|
|
||||||
print('Signature file size: ${signatureFile.lengthSync()} bytes');
|
|
||||||
|
|
||||||
signatureUrl = await controller.uploadBuktiPenerimaan(
|
signatureUrl = await controller.uploadBuktiPenerimaan(
|
||||||
signatureFile.path,
|
signatureFile.path,
|
||||||
isTandaTangan: true,
|
isTandaTangan: true,
|
||||||
);
|
);
|
||||||
print('Berhasil upload tanda tangan: $signatureUrl');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Jika upload tanda tangan gagal, tampilkan pesan dan hentikan proses
|
|
||||||
print('Error upload tanda tangan: $e');
|
|
||||||
throw Exception('Gagal mengupload tanda tangan: $e');
|
throw Exception('Gagal mengupload tanda tangan: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Konfirmasi penerimaan
|
// Konfirmasi penerimaan
|
||||||
try {
|
try {
|
||||||
print('Melakukan konfirmasi penerimaan untuk ID: ${penerima.id}');
|
|
||||||
await controller.konfirmasiPenerimaan(
|
await controller.konfirmasiPenerimaan(
|
||||||
penerima,
|
penerima,
|
||||||
buktiPenerimaan: imageUrl,
|
buktiPenerimaan: imageUrl,
|
||||||
tandaTangan: signatureUrl,
|
tandaTangan: signatureUrl,
|
||||||
);
|
);
|
||||||
print('Konfirmasi penerimaan berhasil');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Jika konfirmasi penerimaan gagal, tampilkan pesan dan hentikan proses
|
|
||||||
print('Error konfirmasi penerimaan: $e');
|
|
||||||
throw Exception('Gagal melakukan konfirmasi penerimaan: $e');
|
throw Exception('Gagal melakukan konfirmasi penerimaan: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hapus file sementara sebelum navigasi
|
// Hapus file sementara sebelum navigasi
|
||||||
try {
|
if (signatureFile.existsSync()) {
|
||||||
if (signatureFile.existsSync()) {
|
await signatureFile.delete();
|
||||||
await signatureFile.delete();
|
}
|
||||||
}
|
if (tempDir.existsSync()) {
|
||||||
if (tempDir.existsSync()) {
|
await tempDir.delete();
|
||||||
await tempDir.delete();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error saat menghapus file sementara: $e');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tutup semua snackbar yang mungkin masih terbuka
|
// Tutup semua snackbar yang mungkin masih terbuka
|
||||||
|
@ -323,144 +323,324 @@ class PengaduanView extends GetView<PengaduanController> {
|
|||||||
formattedDate = DateTimeHelper.formatDate(item.tanggalPengaduan);
|
formattedDate = DateTimeHelper.formatDate(item.tanggalPengaduan);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Card(
|
||||||
width: double.infinity,
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
shape: RoundedRectangleBorder(
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
boxShadow: [
|
side: BorderSide(
|
||||||
BoxShadow(
|
color: statusColor.withOpacity(0.3),
|
||||||
color: Colors.grey.withAlpha(26),
|
width: 1,
|
||||||
spreadRadius: 1,
|
),
|
||||||
blurRadius: 3,
|
|
||||||
offset: const Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Padding(
|
elevation: 3,
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
// Header dengan warna sesuai status
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
width: double.infinity,
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
Expanded(
|
decoration: BoxDecoration(
|
||||||
child: Text(
|
color: statusColor.withOpacity(0.1),
|
||||||
item.warga?['nama'] ?? item.judul ?? '',
|
borderRadius: const BorderRadius.only(
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
topLeft: Radius.circular(12),
|
||||||
fontWeight: FontWeight.bold,
|
topRight: Radius.circular(12),
|
||||||
),
|
|
||||||
// overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
),
|
||||||
Container(
|
child: Row(
|
||||||
padding:
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
children: [
|
||||||
decoration: BoxDecoration(
|
Expanded(
|
||||||
color: statusColor.withOpacity(0.1),
|
child: Row(
|
||||||
borderRadius: BorderRadius.circular(8),
|
children: [
|
||||||
),
|
Icon(
|
||||||
child: Row(
|
Icons.report_problem,
|
||||||
mainAxisSize: MainAxisSize.min,
|
color: statusColor,
|
||||||
children: [
|
),
|
||||||
Icon(
|
const SizedBox(width: 8),
|
||||||
statusIcon,
|
Flexible(
|
||||||
size: 16,
|
child: Text(
|
||||||
color: statusColor,
|
item.warga?['nama'] ?? item.judul ?? '',
|
||||||
),
|
style: TextStyle(
|
||||||
const SizedBox(width: 4),
|
fontSize: 16,
|
||||||
Text(
|
|
||||||
item.status ?? '',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: statusColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: statusColor,
|
||||||
),
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(
|
||||||
|
color: statusColor,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
statusIcon,
|
||||||
|
size: 14,
|
||||||
|
color: statusColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
item.status ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
color: statusColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Deskripsi masalah
|
||||||
|
if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Deskripsi Masalah:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
item.deskripsi ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Informasi penyaluran bantuan jika ada
|
||||||
|
if (item.penerimaPenyaluran != null)
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.blue.shade200,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Penyaluran: ${item.namaPenyaluran ?? "Tidak tersedia"}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Bantuan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.stokBantuan?['nama'] ?? '-',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Jumlah',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${item.jumlahBantuan} ${item.stokBantuan?['satuan'] ?? ''}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Informasi pelapor dan tanggal
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Pelapor',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.warga?['nama_lengkap'] ?? '-',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'NIK',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.warga?['nik'] ?? '-',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
const SizedBox(height: 12),
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
// Informasi tanggal
|
||||||
Text(
|
Container(
|
||||||
item.deskripsi ?? '',
|
padding:
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
maxLines: 2,
|
decoration: BoxDecoration(
|
||||||
overflow: TextOverflow.ellipsis,
|
color: Colors.grey.shade100,
|
||||||
),
|
borderRadius: BorderRadius.circular(4),
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.person,
|
|
||||||
label: 'Pelapor',
|
|
||||||
value: item.warga?['nama_lengkap'] ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.numbers,
|
|
||||||
label: 'NIK',
|
|
||||||
value: item.warga?['nik'] ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
if (item.penerimaPenyaluran != null) ...[
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.shopping_bag,
|
|
||||||
label: 'Jumlah',
|
|
||||||
value:
|
|
||||||
'${item.jumlahBantuan} ${item.stokBantuan['satuan']}',
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.inventory,
|
|
||||||
label: 'Stok Bantuan',
|
|
||||||
value: item.stokBantuan['nama'] ?? '',
|
|
||||||
),
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.calendar_today,
|
||||||
|
size: 12,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
formattedDate,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Tombol detail
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed('/detail-pengaduan',
|
||||||
|
arguments: {'id': item.id});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.info_outline, size: 18),
|
||||||
|
label: const Text('Lihat Detail'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: statusColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 8),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.category,
|
|
||||||
label: 'Nama Penyaluran',
|
|
||||||
value: item.namaPenyaluran ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.calendar_today,
|
|
||||||
label: 'Tanggal Pengaduan',
|
|
||||||
value: formattedDate,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: _buildActionButtons(context, item),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -505,174 +685,19 @@ class PengaduanView extends GetView<PengaduanController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildActionButtons(BuildContext context, dynamic item) {
|
List<Widget> _buildActionButtons(BuildContext context, dynamic item) {
|
||||||
final status = item.status?.toUpperCase();
|
return [
|
||||||
|
TextButton.icon(
|
||||||
if (status == 'MENUNGGU') {
|
onPressed: () {
|
||||||
return [
|
// Navigasi ke halaman detail pengaduan
|
||||||
TextButton.icon(
|
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
||||||
onPressed: () {
|
},
|
||||||
// Implementasi untuk memproses pengaduan
|
icon: const Icon(Icons.info_outline, size: 18),
|
||||||
_showTindakanDialog(context, item);
|
label: const Text('Detail'),
|
||||||
},
|
style: TextButton.styleFrom(
|
||||||
icon: const Icon(Icons.engineering, size: 18),
|
foregroundColor: Colors.grey,
|
||||||
label: const Text('Tindakan'),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.blue,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Navigasi ke halaman detail pengaduan
|
|
||||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.info_outline, size: 18),
|
|
||||||
label: const Text('Detail'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.grey,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} else if (status == 'TINDAKAN') {
|
|
||||||
return [
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Implementasi untuk menyelesaikan pengaduan
|
|
||||||
_showSelesaikanDialog(context, item);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.check_circle, size: 18),
|
|
||||||
label: const Text('Selesaikan'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.green,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Navigasi ke halaman detail pengaduan
|
|
||||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.info_outline, size: 18),
|
|
||||||
label: const Text('Detail'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.grey,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Navigasi ke halaman detail pengaduan
|
|
||||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.info_outline, size: 18),
|
|
||||||
label: const Text('Detail'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.grey,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showTindakanDialog(BuildContext context, dynamic item) {
|
|
||||||
controller.tindakanController.clear();
|
|
||||||
controller.catatanController.clear();
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Tindakan Pengaduan'),
|
|
||||||
content: Form(
|
|
||||||
key: controller.tindakanFormKey,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: controller.tindakanController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Tindakan yang dilakukan',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
validator: controller.validateTindakan,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
TextFormField(
|
|
||||||
controller: controller.catatanController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Catatan (opsional)',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('Batal'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (controller.tindakanFormKey.currentState!.validate()) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
controller.tambahTindakanPengaduan(
|
|
||||||
pengaduanId: item.id!,
|
|
||||||
tindakan: controller.tindakanController.text,
|
|
||||||
kategoriTindakan: 'VERIFIKASI_DATA',
|
|
||||||
statusTindakan: 'PROSES',
|
|
||||||
catatan: controller.catatanController.text.isEmpty
|
|
||||||
? null
|
|
||||||
: controller.catatanController.text,
|
|
||||||
buktiTindakanPaths: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('Simpan'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
];
|
||||||
}
|
|
||||||
|
|
||||||
void _showSelesaikanDialog(BuildContext context, dynamic item) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Selesaikan Pengaduan'),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const Text(
|
|
||||||
'Apakah Anda yakin ingin menyelesaikan pengaduan ini?',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('Batal'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
controller.selesaikanPengaduan(item.id!);
|
|
||||||
},
|
|
||||||
child: const Text('Selesaikan'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,236 +306,443 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withAlpha(26),
|
color: Colors.grey.withOpacity(0.15),
|
||||||
spreadRadius: 1,
|
spreadRadius: 2,
|
||||||
blurRadius: 3,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 1),
|
offset: const Offset(0, 3),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
border: Border.all(
|
||||||
|
color: statusColor.withOpacity(0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: ClipRRect(
|
||||||
padding: const EdgeInsets.all(16.0),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
// Header dengan status
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
Container(
|
||||||
children: [
|
color: statusColor.withOpacity(0.1),
|
||||||
Expanded(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Container(
|
||||||
child: Text(
|
padding: const EdgeInsets.all(6),
|
||||||
donaturNama,
|
decoration: BoxDecoration(
|
||||||
style:
|
color: statusColor.withOpacity(0.2),
|
||||||
Theme.of(context).textTheme.titleMedium?.copyWith(
|
shape: BoxShape.circle,
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
),
|
child: Icon(
|
||||||
overflow: TextOverflow.ellipsis,
|
statusIcon,
|
||||||
|
size: 16,
|
||||||
|
color: statusColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isDonaturManual)
|
const SizedBox(width: 8),
|
||||||
Tooltip(
|
|
||||||
message: 'Donatur Manual (Diinput oleh petugas desa)',
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(left: 4),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6,
|
|
||||||
vertical: 2,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.blue.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
border: Border.all(color: Colors.blue.shade300),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'Manual',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
color: Colors.blue,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: statusColor.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
statusIcon,
|
|
||||||
size: 16,
|
|
||||||
color: statusColor,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
Text(
|
||||||
item.status ?? 'Tidak diketahui',
|
item.status ?? 'Tidak diketahui',
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: statusColor,
|
color: statusColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
DateTimeHelper.formatDate(item.createdAt),
|
||||||
),
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
const SizedBox(height: 12),
|
color: Colors.grey.shade700,
|
||||||
Row(
|
fontStyle: FontStyle.italic,
|
||||||
children: [
|
),
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: isUang ? Icons.monetization_on : Icons.category,
|
|
||||||
label: 'Kategori Bantuan',
|
|
||||||
value: kategoriNama,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon:
|
|
||||||
isUang ? Icons.account_balance_wallet : Icons.inventory,
|
|
||||||
label: 'Jumlah',
|
|
||||||
value: isUang
|
|
||||||
? 'Rp ${DateTimeHelper.formatNumber(item.jumlah)}'
|
|
||||||
: '${DateTimeHelper.formatNumber(item.jumlah)} $kategoriSatuan',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.calendar_today,
|
|
||||||
label: 'Tanggal Dibuat',
|
|
||||||
value: DateTimeHelper.formatDateTime(item.createdAt,
|
|
||||||
defaultValue: 'Tidak ada tanggal'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: item.status == 'TERVERIFIKASI' &&
|
|
||||||
item.petugasDesaId != null
|
|
||||||
? _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.person,
|
|
||||||
label: 'Diverifikasi Oleh',
|
|
||||||
value:
|
|
||||||
controller.getPetugasDesaNama(item.petugasDesaId),
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// Tampilkan thumbnail foto bantuan jika ada
|
|
||||||
if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.photo_library,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'Foto Bantuan',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'(${item.fotoBantuan!.length} foto)',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.blue,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
// Content
|
||||||
if (item.status == 'MENUNGGU')
|
Padding(
|
||||||
Row(
|
padding: const EdgeInsets.all(16),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TextButton.icon(
|
// Donatur info
|
||||||
onPressed: () {
|
Row(
|
||||||
_showVerifikasiDialog(context, item.id ?? '');
|
children: [
|
||||||
},
|
CircleAvatar(
|
||||||
icon: const Icon(Icons.check, size: 18),
|
backgroundColor: AppTheme.primaryColor.withOpacity(0.1),
|
||||||
label: const Text('Terima'),
|
radius: 20,
|
||||||
style: TextButton.styleFrom(
|
child: Text(
|
||||||
foregroundColor: Colors.green,
|
donaturNama.substring(0, 1).toUpperCase(),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
style: TextStyle(
|
||||||
),
|
color: AppTheme.primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
donaturNama,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium
|
||||||
|
?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isDonaturManual)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(left: 4),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.blue.shade300),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Manual',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Donatur',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
const SizedBox(height: 16),
|
||||||
_showTolakDialog(context, item.id ?? '');
|
const Divider(),
|
||||||
},
|
const SizedBox(height: 12),
|
||||||
icon: const Icon(Icons.close, size: 18),
|
|
||||||
label: const Text('Tolak'),
|
// Informasi bantuan
|
||||||
style: TextButton.styleFrom(
|
Row(
|
||||||
foregroundColor: Colors.red,
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
Expanded(
|
||||||
),
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isUang
|
||||||
|
? Colors.green.withOpacity(0.1)
|
||||||
|
: Colors.blue.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
isUang
|
||||||
|
? Icons.monetization_on
|
||||||
|
: Icons.category,
|
||||||
|
size: 16,
|
||||||
|
color: isUang ? Colors.green : Colors.blue,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Kategori',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(
|
||||||
|
color: isUang
|
||||||
|
? Colors.green
|
||||||
|
: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
kategoriNama,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall
|
||||||
|
?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isUang
|
||||||
|
? Colors.amber.withOpacity(0.1)
|
||||||
|
: Colors.purple.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
isUang
|
||||||
|
? Icons.account_balance_wallet
|
||||||
|
: Icons.inventory,
|
||||||
|
size: 16,
|
||||||
|
color: isUang
|
||||||
|
? Colors.amber.shade800
|
||||||
|
: Colors.purple,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Jumlah',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(
|
||||||
|
color: isUang
|
||||||
|
? Colors.amber.shade800
|
||||||
|
: Colors.purple,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
isUang
|
||||||
|
? 'Rp ${DateTimeHelper.formatNumber(item.jumlah)}'
|
||||||
|
: '${DateTimeHelper.formatNumber(item.jumlah)} $kategoriSatuan',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall
|
||||||
|
?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
// Tampilkan thumbnail foto bantuan jika ada
|
||||||
_showDetailDialog(context, item, donaturNama);
|
if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty)
|
||||||
},
|
Column(
|
||||||
icon: const Icon(Icons.info_outline, size: 18),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
label: const Text('Detail'),
|
children: [
|
||||||
style: TextButton.styleFrom(
|
const SizedBox(height: 12),
|
||||||
foregroundColor: Colors.blue,
|
const Divider(),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.photo_library,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Foto Bantuan',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(
|
||||||
|
color: Colors.grey.shade700,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${item.fotoBantuan!.length} foto',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(
|
||||||
|
color: Colors.blue,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (item.status == 'TERVERIFIKASI' &&
|
||||||
|
item.petugasDesaId != null)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.verified_user,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Diverifikasi oleh ',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey.shade700),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: controller.getPetugasDesaNama(
|
||||||
|
item.petugasDesaId),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Footer dengan tombol aksi
|
||||||
|
if (item.status == 'MENUNGGU')
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: Colors.grey.shade200),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_showDetailDialog(context, item, donaturNama);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.info_outline, size: 16),
|
||||||
|
label: const Text('Detail'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.blue,
|
||||||
|
side: BorderSide(color: Colors.blue.shade300),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_showTolakDialog(context, item.id ?? '');
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close, size: 16),
|
||||||
|
label: const Text('Tolak'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
side: BorderSide(color: Colors.red.shade300),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_showVerifikasiDialog(context, item.id ?? '');
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.check, size: 16),
|
||||||
|
label: const Text('Terima'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Row(
|
Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
decoration: BoxDecoration(
|
||||||
children: [
|
color: Colors.grey.shade50,
|
||||||
TextButton.icon(
|
border: Border(
|
||||||
onPressed: () {
|
top: BorderSide(color: Colors.grey.shade200),
|
||||||
_showDetailDialog(context, item, donaturNama);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.info_outline, size: 18),
|
|
||||||
label: const Text('Detail'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.blue,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_showDetailDialog(context, item, donaturNama);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.info_outline, size: 16),
|
||||||
|
label: const Text('Lihat Detail'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -75,8 +75,8 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
// Jadwal hari ini
|
// Jadwal hari ini
|
||||||
JadwalSectionWidget(
|
JadwalSectionWidget(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
title: 'Hari Ini',
|
title: 'Penyaluran Aktif',
|
||||||
jadwalList: controller.jadwalHariIni,
|
jadwalList: controller.jadwalAktif,
|
||||||
status: 'Aktif',
|
status: 'Aktif',
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
context,
|
context,
|
||||||
icon: Icons.event_available,
|
icon: Icons.event_available,
|
||||||
title: 'Aktif',
|
title: 'Aktif',
|
||||||
value: '${controller.jadwalHariIni.length}',
|
value: '${controller.jadwalAktif.length}',
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
@ -343,9 +343,6 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
activeIcon: Icons.warning_amber,
|
activeIcon: Icons.warning_amber,
|
||||||
title: 'Pengaduan',
|
title: 'Pengaduan',
|
||||||
isSelected: controller.activeTabIndex.value == 3,
|
isSelected: controller.activeTabIndex.value == 3,
|
||||||
badge: controller.jumlahDiproses.value > 0
|
|
||||||
? controller.jumlahDiproses.value.toString()
|
|
||||||
: null,
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
controller.changeTab(3);
|
controller.changeTab(3);
|
||||||
@ -675,7 +672,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(2),
|
padding: const EdgeInsets.all(2),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.red,
|
color: Colors.orange,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
@ -705,7 +702,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(2),
|
padding: const EdgeInsets.all(2),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.red,
|
color: Colors.orange,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -332,176 +332,283 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStokBantuanItem(BuildContext context, StokBantuanModel item) {
|
Widget _buildStokBantuanItem(BuildContext context, StokBantuanModel item) {
|
||||||
|
// Tentukan warna berdasarkan jenis bantuan
|
||||||
|
Color categoryColor =
|
||||||
|
item.isUang == true ? Colors.amber.shade700 : AppTheme.primaryColor;
|
||||||
|
|
||||||
|
// Cek apakah stok hampir habis (kurang dari 10)
|
||||||
|
bool isLowStock = !item.isUang! && item.totalStok! < 10;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withAlpha(26),
|
color: Colors.grey.withAlpha(30),
|
||||||
spreadRadius: 1,
|
spreadRadius: 1,
|
||||||
blurRadius: 3,
|
blurRadius: 6,
|
||||||
offset: const Offset(0, 1),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Column(
|
||||||
padding: const EdgeInsets.all(16.0),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// Header dengan gradient berdasarkan jenis bantuan
|
||||||
children: [
|
ClipRRect(
|
||||||
Row(
|
borderRadius: const BorderRadius.only(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
topLeft: Radius.circular(16),
|
||||||
children: [
|
topRight: Radius.circular(16),
|
||||||
Expanded(
|
),
|
||||||
child: Text(
|
child: Container(
|
||||||
item.nama ?? 'Tanpa Nama',
|
width: double.infinity,
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
fontWeight: FontWeight.bold,
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
categoryColor.withOpacity(0.8),
|
||||||
|
categoryColor,
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
item.nama ?? 'Tanpa Nama',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
item.isUang == true
|
||||||
|
? Icons.monetization_on
|
||||||
|
: Icons.inventory_2_outlined,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
item.kategoriBantuan != null
|
||||||
|
? (item.kategoriBantuan!['nama'] ??
|
||||||
|
'Tidak Ada Kategori')
|
||||||
|
: 'Tidak Ada Kategori',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Body content
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Deskripsi
|
||||||
|
if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
|
child: Text(
|
||||||
|
item.deskripsi!,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Detail stok/dana dalam card
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isLowStock
|
||||||
|
? Colors.red.shade50
|
||||||
|
: (item.isUang == true
|
||||||
|
? Colors.amber.shade50
|
||||||
|
: Colors.blue.shade50),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isLowStock
|
||||||
|
? Colors.red.shade200
|
||||||
|
: (item.isUang == true
|
||||||
|
? Colors.amber.shade200
|
||||||
|
: Colors.blue.shade200),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
item.isUang == true
|
||||||
|
? Icons.monetization_on
|
||||||
|
: (isLowStock
|
||||||
|
? Icons.warning_amber_rounded
|
||||||
|
: Icons.inventory),
|
||||||
|
size: 20,
|
||||||
|
color: isLowStock
|
||||||
|
? Colors.red.shade800
|
||||||
|
: (item.isUang == true
|
||||||
|
? Colors.amber.shade800
|
||||||
|
: Colors.blue.shade800),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.isUang == true
|
||||||
|
? 'Total Dana'
|
||||||
|
: (isLowStock
|
||||||
|
? 'Stok Hampir Habis!'
|
||||||
|
: 'Total Stok'),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: isLowStock
|
||||||
|
? Colors.red.shade800
|
||||||
|
: (item.isUang == true
|
||||||
|
? Colors.amber.shade800
|
||||||
|
: Colors.blue.shade800),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.isUang == true
|
||||||
|
? 'Rp ${DateTimeHelper.formatNumber(item.totalStok)}'
|
||||||
|
: '${DateTimeHelper.formatNumber(item.totalStok)} ${item.satuan ?? ''}',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleLarge
|
||||||
|
?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: isLowStock
|
||||||
|
? Colors.red.shade900
|
||||||
|
: (item.isUang == true
|
||||||
|
? Colors.amber.shade900
|
||||||
|
: Colors.blue.shade900),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
padding:
|
const SizedBox(height: 16),
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
// Additional details
|
||||||
color: AppTheme.primaryColor.withOpacity(0.1),
|
Row(
|
||||||
borderRadius: BorderRadius.circular(8),
|
children: [
|
||||||
),
|
Icon(
|
||||||
child: Row(
|
Icons.access_time,
|
||||||
mainAxisSize: MainAxisSize.min,
|
size: 16,
|
||||||
children: [
|
color: Colors.grey[600],
|
||||||
if (item.isUang == true)
|
),
|
||||||
const Icon(
|
const SizedBox(width: 4),
|
||||||
Icons.monetization_on,
|
Expanded(
|
||||||
size: 16,
|
child: Text(
|
||||||
color: AppTheme.primaryColor,
|
item.updatedAt != null
|
||||||
),
|
? 'Diperbarui: ${DateTimeHelper.formatDateTimeWithHour(item.updatedAt!)}'
|
||||||
if (item.isUang == true) const SizedBox(width: 4),
|
: 'Tidak ada data pembaruan',
|
||||||
Text(
|
|
||||||
item.kategoriBantuan != null
|
|
||||||
? (item.kategoriBantuan!['nama'] ??
|
|
||||||
'Tidak Ada Kategori')
|
|
||||||
: 'Tidak Ada Kategori',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: AppTheme.primaryColor,
|
color: Colors.grey[600],
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Tombol Aksi
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: Colors.grey.shade200),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 8, horizontal: 0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan dialog edit stok bantuan
|
||||||
|
_showEditStokDialog(context, item);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.edit_outlined, size: 16),
|
||||||
|
label: const Text('Edit'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.blue,
|
||||||
|
side: BorderSide(color: Colors.blue.shade300),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan dialog konfirmasi hapus
|
||||||
|
_showDeleteConfirmation(context, item);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 16),
|
||||||
|
label: const Text('Hapus'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
side: BorderSide(color: Colors.red.shade300),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 4.0),
|
|
||||||
child: Text(
|
|
||||||
item.deskripsi!,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: item.isUang == true
|
|
||||||
? Icons.monetization_on
|
|
||||||
: Icons.inventory,
|
|
||||||
label: item.isUang == true ? 'Total Dana' : 'Total Stok',
|
|
||||||
value: item.isUang == true
|
|
||||||
? 'Rp ${DateTimeHelper.formatNumber(item.totalStok)}'
|
|
||||||
: '${DateTimeHelper.formatNumber(item.totalStok)} ${item.satuan ?? ''}',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.access_time,
|
|
||||||
label: 'Terakhir Diperbarui',
|
|
||||||
value: item.updatedAt != null
|
|
||||||
? '${item.updatedAt!.day}/${item.updatedAt!.month}/${item.updatedAt!.year} ${item.updatedAt!.hour}:${item.updatedAt!.minute}'
|
|
||||||
: 'Tidak ada data',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Tampilkan dialog edit stok bantuan
|
|
||||||
_showEditStokDialog(context, item);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.edit_outlined, size: 18),
|
|
||||||
label: const Text('Edit'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.blue,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Tampilkan dialog konfirmasi hapus
|
|
||||||
_showDeleteConfirmation(context, item);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.delete_outline, size: 18),
|
|
||||||
label: const Text('Hapus'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.red,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildItemDetail(
|
|
||||||
BuildContext context, {
|
|
||||||
required IconData icon,
|
|
||||||
required String label,
|
|
||||||
required String value,
|
|
||||||
}) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
icon,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Skema Bantuan
|
// Skema Bantuan
|
||||||
Text(
|
Text(
|
||||||
@ -175,6 +175,18 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await loadPengajuanKelayakan(value);
|
await loadPengajuanKelayakan(value);
|
||||||
|
|
||||||
|
// Periksa apakah ada penerima
|
||||||
|
if (jumlahPenerima.value == 0) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Perhatian',
|
||||||
|
'Skema bantuan ini tidak memiliki penerima yang terverifikasi!',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
duration: const Duration(seconds: 4),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
@ -184,6 +196,37 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
// const SizedBox(height: 16),
|
||||||
|
// Pesan pemberitahuan jika tidak ada penerima
|
||||||
|
Obx(() => jumlahPenerima.value == 0 &&
|
||||||
|
selectedSkemaBantuanId.value != null
|
||||||
|
? Container(
|
||||||
|
margin: const EdgeInsets.only(top: 16),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.red.shade200),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.warning_amber_rounded,
|
||||||
|
color: Colors.red.shade700),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Skema bantuan ini tidak memiliki penerima yang terverifikasi. Tambahkan penerima terlebih dahulu.',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red.shade700,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox()),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Jumlah Penerima (Otomatis)
|
// Jumlah Penerima (Otomatis)
|
||||||
Row(
|
Row(
|
||||||
@ -755,67 +798,78 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
// Tombol Submit
|
// Tombol Submit
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: Obx(() => ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: jumlahPenerima.value > 0
|
||||||
if (formKey.currentState!.validate()) {
|
? () {
|
||||||
// Periksa kecukupan stok
|
if (formKey.currentState!.validate()) {
|
||||||
if (!isStokCukup.value) {
|
// Periksa kecukupan stok
|
||||||
Get.snackbar(
|
if (!isStokCukup.value) {
|
||||||
'Stok Tidak Cukup',
|
Get.snackbar(
|
||||||
'Stok bantuan tidak mencukupi untuk penyaluran ini. Silakan tambah stok terlebih dahulu.',
|
'Stok Tidak Cukup',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
'Stok bantuan tidak mencukupi untuk penyaluran ini. Silakan tambah stok terlebih dahulu.',
|
||||||
backgroundColor: Colors.red,
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
colorText: Colors.white,
|
backgroundColor: Colors.red,
|
||||||
duration: const Duration(seconds: 4),
|
colorText: Colors.white,
|
||||||
);
|
duration: const Duration(seconds: 4),
|
||||||
return;
|
);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Gabungkan tanggal dan waktu mulai
|
// Gabungkan tanggal dan waktu mulai
|
||||||
DateTime? tanggalWaktuMulai;
|
DateTime? tanggalWaktuMulai;
|
||||||
if (selectedDate.value != null &&
|
if (selectedDate.value != null &&
|
||||||
selectedWaktuMulai.value != null) {
|
selectedWaktuMulai.value != null) {
|
||||||
tanggalWaktuMulai = DateTime(
|
tanggalWaktuMulai = DateTime(
|
||||||
selectedDate.value!.year,
|
selectedDate.value!.year,
|
||||||
selectedDate.value!.month,
|
selectedDate.value!.month,
|
||||||
selectedDate.value!.day,
|
selectedDate.value!.day,
|
||||||
selectedWaktuMulai.value!.hour,
|
selectedWaktuMulai.value!.hour,
|
||||||
selectedWaktuMulai.value!.minute,
|
selectedWaktuMulai.value!.minute,
|
||||||
).toLocal();
|
).toLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panggil fungsi untuk menambahkan penyaluran
|
// Panggil fungsi untuk menambahkan penyaluran
|
||||||
controller.tambahPenyaluran(
|
controller.tambahPenyaluran(
|
||||||
nama: namaController.text,
|
nama: namaController.text,
|
||||||
deskripsi: deskripsiController.text,
|
deskripsi: deskripsiController.text,
|
||||||
skemaId: selectedSkemaBantuanId.value!,
|
skemaId: selectedSkemaBantuanId.value!,
|
||||||
lokasiPenyaluranId: selectedLokasiPenyaluranId.value!,
|
lokasiPenyaluranId:
|
||||||
jumlahPenerima: jumlahPenerima.value,
|
selectedLokasiPenyaluranId.value!,
|
||||||
tanggalPenyaluran: tanggalWaktuMulai,
|
jumlahPenerima: jumlahPenerima.value,
|
||||||
kategoriBantuanId:
|
tanggalPenyaluran: tanggalWaktuMulai,
|
||||||
selectedSkemaBantuan.value!.kategoriBantuanId!,
|
kategoriBantuanId: selectedSkemaBantuan
|
||||||
jumlahDiterimaPerOrang: jumlahDiterimaPerOrang.value,
|
.value!.kategoriBantuanId!,
|
||||||
stokBantuanId:
|
jumlahDiterimaPerOrang:
|
||||||
selectedSkemaBantuan.value!.stokBantuanId!,
|
jumlahDiterimaPerOrang.value,
|
||||||
totalStokDibutuhkan: totalStokDibutuhkan.value);
|
stokBantuanId: selectedSkemaBantuan
|
||||||
}
|
.value!.stokBantuanId!,
|
||||||
},
|
totalStokDibutuhkan:
|
||||||
style: ElevatedButton.styleFrom(
|
totalStokDibutuhkan.value);
|
||||||
backgroundColor: AppTheme.primaryColor,
|
|
||||||
foregroundColor: Colors.white,
|
//get back and refresh page
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
Get.back();
|
||||||
shape: RoundedRectangleBorder(
|
controller.refreshData();
|
||||||
borderRadius: BorderRadius.circular(8),
|
}
|
||||||
),
|
}
|
||||||
),
|
: null,
|
||||||
child: const Text(
|
style: ElevatedButton.styleFrom(
|
||||||
'Simpan Penyaluran',
|
backgroundColor: AppTheme.primaryColor,
|
||||||
style: TextStyle(
|
foregroundColor: Colors.white,
|
||||||
fontSize: 16,
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
fontWeight: FontWeight.bold,
|
shape: RoundedRectangleBorder(
|
||||||
),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
disabledBackgroundColor: Colors.grey.shade300,
|
||||||
|
disabledForegroundColor: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Simpan Penyaluran',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -44,6 +44,12 @@ class WargaDashboardController extends GetxController {
|
|||||||
// Jumlah notifikasi belum dibaca
|
// Jumlah notifikasi belum dibaca
|
||||||
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
||||||
|
|
||||||
|
// Variabel untuk mengontrol auto-refresh
|
||||||
|
final RxBool _autoRefreshEnabled = true.obs;
|
||||||
|
|
||||||
|
// Getter untuk status auto-refresh
|
||||||
|
bool get isAutoRefreshEnabled => _autoRefreshEnabled.value;
|
||||||
|
|
||||||
// Getter untuk data user
|
// Getter untuk data user
|
||||||
BaseUserModel? get user => _authController.baseUser;
|
BaseUserModel? get user => _authController.baseUser;
|
||||||
String get role => user?.role ?? 'WARGA';
|
String get role => user?.role ?? 'WARGA';
|
||||||
@ -74,6 +80,14 @@ class WargaDashboardController extends GetxController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getter untuk NIK
|
||||||
|
String? get nik {
|
||||||
|
if (_authController.isWarga && _authController.roleData != null) {
|
||||||
|
return (_authController.roleData as WargaModel).nik;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Getter untuk foto profil
|
// Getter untuk foto profil
|
||||||
String? get profilePhotoUrl {
|
String? get profilePhotoUrl {
|
||||||
// 1. Coba ambil dari fotoProfil yang sudah disimpan
|
// 1. Coba ambil dari fotoProfil yang sudah disimpan
|
||||||
@ -121,35 +135,18 @@ class WargaDashboardController extends GetxController {
|
|||||||
void loadUserData() {
|
void loadUserData() {
|
||||||
currentUser.value = _authController.baseUser;
|
currentUser.value = _authController.baseUser;
|
||||||
|
|
||||||
// Tambahkan log debugging
|
// Kurangi log debugging
|
||||||
print('DEBUG WARGA: Memuat data user dari AuthController');
|
|
||||||
print('DEBUG WARGA: baseUser: ${_authController.baseUser}');
|
|
||||||
print('DEBUG WARGA: roleData: ${_authController.roleData}');
|
|
||||||
print('DEBUG WARGA: nama yang akan ditampilkan: $nama');
|
|
||||||
print(
|
|
||||||
'DEBUG WARGA: displayName dari auth controller: ${_authController.displayName}');
|
|
||||||
|
|
||||||
if (_authController.userData != null) {
|
if (_authController.userData != null) {
|
||||||
print(
|
|
||||||
'DEBUG WARGA: userData ada, role: ${_authController.userData!.baseUser.roleName}');
|
|
||||||
|
|
||||||
if (_authController.isWarga) {
|
if (_authController.isWarga) {
|
||||||
print('DEBUG WARGA: User adalah warga');
|
|
||||||
var wargaData = _authController.roleData;
|
var wargaData = _authController.roleData;
|
||||||
print('DEBUG WARGA: Data warga: ${wargaData?.namaLengkap}');
|
|
||||||
|
|
||||||
// Ambil foto profil dari wargaData jika ada
|
// Ambil foto profil dari wargaData jika ada
|
||||||
if (wargaData != null &&
|
if (wargaData != null &&
|
||||||
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 dari roleData: ${fotoProfil.value}');
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
print('DEBUG WARGA: User bukan warga');
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
print('DEBUG WARGA: userData null');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ambil foto profil dari database
|
// Ambil foto profil dari database
|
||||||
@ -169,9 +166,6 @@ class WargaDashboardController extends GetxController {
|
|||||||
|
|
||||||
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}');
|
|
||||||
} 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');
|
||||||
@ -211,18 +205,13 @@ class WargaDashboardController extends GetxController {
|
|||||||
// Reset data terlebih dahulu untuk memastikan tidak ada data lama yang tersimpan
|
// Reset data terlebih dahulu untuk memastikan tidak ada data lama yang tersimpan
|
||||||
penerimaPenyaluran.clear();
|
penerimaPenyaluran.clear();
|
||||||
|
|
||||||
// Log untuk debugging
|
|
||||||
print('DEBUG PENERIMAAN: Memulai fetchPenerimaPenyaluran()');
|
|
||||||
|
|
||||||
// Pastikan user sudah login dan memiliki ID
|
// Pastikan user sudah login dan memiliki ID
|
||||||
if (user?.id == null) {
|
if (user?.id == null) {
|
||||||
print('DEBUG PENERIMAAN: User ID null, tidak bisa mengambil data');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gunakan langsung ID pengguna sebagai warga_id
|
// Gunakan langsung ID pengguna sebagai warga_id
|
||||||
final wargaId = user!.id;
|
final wargaId = user!.id;
|
||||||
print('DEBUG PENERIMAAN: Mengambil data untuk warga ID: $wargaId');
|
|
||||||
|
|
||||||
// Ambil data penerima penyaluran dengan join ke warga, stok bantuan, dan penyaluran bantuan
|
// Ambil data penerima penyaluran dengan join ke warga, stok bantuan, dan penyaluran bantuan
|
||||||
final response =
|
final response =
|
||||||
@ -240,9 +229,6 @@ class WargaDashboardController extends GetxController {
|
|||||||
)
|
)
|
||||||
''').eq('warga_id', wargaId).order('created_at', ascending: false);
|
''').eq('warga_id', wargaId).order('created_at', ascending: false);
|
||||||
|
|
||||||
print(
|
|
||||||
'DEBUG PENERIMAAN: Respons diterima dengan ${response.length} item');
|
|
||||||
|
|
||||||
final List<PenerimaPenyaluranModel> penerima = [];
|
final List<PenerimaPenyaluranModel> penerima = [];
|
||||||
|
|
||||||
// Loop melalui setiap data penerima
|
// Loop melalui setiap data penerima
|
||||||
@ -329,7 +315,6 @@ class WargaDashboardController extends GetxController {
|
|||||||
|
|
||||||
var model = PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData);
|
var model = PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData);
|
||||||
penerima.add(model);
|
penerima.add(model);
|
||||||
print('DEBUG PENERIMAAN: Berhasil parse item: ${model.id}');
|
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
print('DEBUG PENERIMAAN: Error parsing item: $parseError');
|
print('DEBUG PENERIMAAN: Error parsing item: $parseError');
|
||||||
print('DEBUG PENERIMAAN: Data yang gagal di-parse: $item');
|
print('DEBUG PENERIMAAN: Data yang gagal di-parse: $item');
|
||||||
@ -340,19 +325,10 @@ class WargaDashboardController extends GetxController {
|
|||||||
if (penerima.isNotEmpty) {
|
if (penerima.isNotEmpty) {
|
||||||
// Update nilai observable
|
// Update nilai observable
|
||||||
penerimaPenyaluran.assignAll(penerima);
|
penerimaPenyaluran.assignAll(penerima);
|
||||||
print(
|
|
||||||
'DEBUG PENERIMAAN: Berhasil assign ${penerima.length} item ke list');
|
|
||||||
|
|
||||||
var diterima =
|
var diterima =
|
||||||
penerima.where((p) => p.statusPenerimaan == 'DITERIMA').length;
|
penerima.where((p) => p.statusPenerimaan == 'DITERIMA').length;
|
||||||
totalPenyaluranDiterima.value = diterima;
|
totalPenyaluranDiterima.value = diterima;
|
||||||
|
|
||||||
// Log untuk debugging
|
|
||||||
print(
|
|
||||||
'Berhasil memuat ${penerima.length} data penerimaan untuk warga ID: $wargaId');
|
|
||||||
} else {
|
|
||||||
print(
|
|
||||||
'DEBUG PENERIMAAN: Tidak ada data penerimaan yang berhasil di-parse');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return penerima;
|
return penerima;
|
||||||
@ -628,12 +604,23 @@ class WargaDashboardController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk mengatur apakah auto-refresh diaktifkan atau tidak
|
||||||
|
void setAutoRefreshEnabled(bool enabled) {
|
||||||
|
_autoRefreshEnabled.value = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
// Metode untuk refresh data setelah update profil atau kembali ke halaman
|
// Metode untuk refresh data setelah update profil atau kembali ke halaman
|
||||||
Future<void> refreshData() async {
|
Future<void> refreshData({bool silent = false}) async {
|
||||||
print('DEBUG WARGA: Memulai refresh data...');
|
// Cek apakah auto-refresh diaktifkan
|
||||||
|
if (!_autoRefreshEnabled.value) {
|
||||||
|
if (!silent) print('Auto-refresh dinonaktifkan, melewati refresh data');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!silent) print('Memulai refresh data...');
|
||||||
await _authController.refreshUserData(); // Refresh data dari server
|
await _authController.refreshUserData(); // Refresh data dari server
|
||||||
loadUserData(); // Muat ulang data ke variabel lokal
|
loadUserData(); // Muat ulang data ke variabel lokal
|
||||||
fetchData(); // Ambil data terkait lainnya
|
fetchData(); // Ambil data terkait lainnya
|
||||||
print('DEBUG WARGA: Refresh data selesai');
|
if (!silent) print('Refresh data selesai');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
306
lib/app/modules/warga/views/form_pengaduan_view.dart
Normal file
306
lib/app/modules/warga/views/form_pengaduan_view.dart
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
|
||||||
|
|
||||||
|
class FormPengaduanView extends StatefulWidget {
|
||||||
|
final String uidPenerimaan;
|
||||||
|
final String? judul;
|
||||||
|
final String? deskripsi;
|
||||||
|
final List<File>? selectedImages;
|
||||||
|
|
||||||
|
const FormPengaduanView({
|
||||||
|
Key? key,
|
||||||
|
required this.uidPenerimaan,
|
||||||
|
this.judul,
|
||||||
|
this.deskripsi,
|
||||||
|
this.selectedImages,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FormPengaduanView> createState() => _FormPengaduanViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FormPengaduanViewState extends State<FormPengaduanView> {
|
||||||
|
final WargaDashboardController wargaController =
|
||||||
|
Get.find<WargaDashboardController>();
|
||||||
|
|
||||||
|
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
final TextEditingController _titleController = TextEditingController();
|
||||||
|
final TextEditingController _descriptionController = TextEditingController();
|
||||||
|
|
||||||
|
bool _isSubmitting = false;
|
||||||
|
List<File> _selectedImages = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
wargaController.setAutoRefreshEnabled(false);
|
||||||
|
|
||||||
|
// Isi form dengan data yang diberikan jika ada
|
||||||
|
if (widget.judul != null) {
|
||||||
|
_titleController.text = widget.judul!;
|
||||||
|
}
|
||||||
|
if (widget.deskripsi != null) {
|
||||||
|
_descriptionController.text = widget.deskripsi!;
|
||||||
|
}
|
||||||
|
if (widget.selectedImages != null && widget.selectedImages!.isNotEmpty) {
|
||||||
|
_selectedImages = List.from(widget.selectedImages!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_titleController.dispose();
|
||||||
|
_descriptionController.dispose();
|
||||||
|
wargaController.setAutoRefreshEnabled(true);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickImage(ImageSource source) async {
|
||||||
|
try {
|
||||||
|
final ImagePicker picker = ImagePicker();
|
||||||
|
final XFile? image = await picker.pickImage(
|
||||||
|
source: source,
|
||||||
|
imageQuality: 80,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (image != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedImages.add(File(image.path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Tidak dapat memilih gambar: $e',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeImage(int index) {
|
||||||
|
setState(() {
|
||||||
|
_selectedImages.removeAt(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submitComplaint() async {
|
||||||
|
if (_formKey.currentState!.validate() && !_isSubmitting) {
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
_isSubmitting = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Menutup keyboard sebelum memproses
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
|
||||||
|
// Menyiapkan data foto
|
||||||
|
List<String> fotoPaths =
|
||||||
|
_selectedImages.map((file) => file.path).toList();
|
||||||
|
|
||||||
|
final success = await wargaController.addPengaduan(
|
||||||
|
judul: _titleController.text,
|
||||||
|
deskripsi: _descriptionController.text,
|
||||||
|
penerimaPenyaluranId: widget.uidPenerimaan,
|
||||||
|
fotoPengaduanPaths: fotoPaths,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Get.back(result: true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal mengirim pengaduan: $e',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isSubmitting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Form Pengaduan'),
|
||||||
|
backgroundColor: Get.theme.primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Input Judul Pengaduan
|
||||||
|
TextFormField(
|
||||||
|
controller: _titleController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Judul Pengaduan',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||||
|
),
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Judul pengaduan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Input Deskripsi Pengaduan
|
||||||
|
TextFormField(
|
||||||
|
controller: _descriptionController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Deskripsi Pengaduan',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||||
|
),
|
||||||
|
maxLines: 5,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Deskripsi pengaduan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Button untuk memilih gambar
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () => _pickImage(ImageSource.gallery),
|
||||||
|
icon: const Icon(Icons.photo_library),
|
||||||
|
label: const Text('Galeri'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () => _pickImage(ImageSource.camera),
|
||||||
|
icon: const Icon(Icons.camera_alt),
|
||||||
|
label: const Text('Kamera'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Tampilkan gambar-gambar jika sudah dipilih
|
||||||
|
if (_selectedImages.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Foto Pengaduan:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
height: 120,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: _selectedImages.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
margin: const EdgeInsets.only(right: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
image: DecorationImage(
|
||||||
|
image: FileImage(_selectedImages[index]),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 5,
|
||||||
|
right: 13,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _removeImage(index),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// Button untuk submit pengaduan
|
||||||
|
SizedBox(
|
||||||
|
height: 48,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _isSubmitting ? null : _submitComplaint,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: Colors.orange.shade700,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _isSubmitting
|
||||||
|
? const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
'Kirim Pengaduan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,11 @@ import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_con
|
|||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
import 'package:penyaluran_app/app/widgets/bantuan_card.dart';
|
import 'package:penyaluran_app/app/widgets/bantuan_card.dart';
|
||||||
import 'package:penyaluran_app/app/widgets/section_header.dart';
|
import 'package:penyaluran_app/app/widgets/section_header.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/penerima_penyaluran_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/warga/views/form_pengaduan_view.dart';
|
||||||
|
|
||||||
class WargaDashboardView extends GetView<WargaDashboardController> {
|
class WargaDashboardView extends GetView<WargaDashboardController> {
|
||||||
const WargaDashboardView({super.key});
|
const WargaDashboardView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -16,7 +17,6 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
if (controller.isLoading.value) {
|
if (controller.isLoading.value) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
controller.fetchData();
|
controller.fetchData();
|
||||||
@ -150,10 +150,10 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
icon: Icons.home_rounded,
|
icon: Icons.numbers_rounded,
|
||||||
iconColor: Colors.blue.shade300,
|
iconColor: Colors.green.shade300,
|
||||||
label: 'Alamat',
|
label: 'NIK',
|
||||||
value: controller.alamat ?? 'Alamat tidak tersedia',
|
value: controller.nik ?? 'NIK tidak tersedia',
|
||||||
),
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8),
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
@ -175,31 +175,19 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
label: 'Desa',
|
label: 'Desa',
|
||||||
value: controller.desa ?? 'Desa tidak tersedia',
|
value: controller.desa ?? 'Desa tidak tersedia',
|
||||||
),
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Divider(height: 1),
|
||||||
|
),
|
||||||
|
_buildInfoRow(
|
||||||
|
icon: Icons.home_rounded,
|
||||||
|
iconColor: Colors.blue.shade300,
|
||||||
|
label: 'Alamat Lengkap',
|
||||||
|
value: controller.alamat ?? 'Alamat tidak tersedia',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildActionButton(
|
|
||||||
icon: Icons.edit_rounded,
|
|
||||||
label: 'Edit Profil',
|
|
||||||
color: Colors.blue.shade700,
|
|
||||||
onTap: () => Get.toNamed(Routes.PROFILE),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: _buildActionButton(
|
|
||||||
icon: Icons.notifications_rounded,
|
|
||||||
label: 'Notifikasi',
|
|
||||||
color: Colors.amber.shade700,
|
|
||||||
onTap: () => Get.toNamed(Routes.NOTIFIKASI),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -299,7 +287,6 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatisticSection() {
|
Widget _buildStatisticSection() {
|
||||||
// Data untuk statistik
|
|
||||||
final totalBantuan = controller.penerimaPenyaluran.length;
|
final totalBantuan = controller.penerimaPenyaluran.length;
|
||||||
final totalDiterima = controller.penerimaPenyaluran
|
final totalDiterima = controller.penerimaPenyaluran
|
||||||
.where((item) => item.statusPenerimaan == 'DITERIMA')
|
.where((item) => item.statusPenerimaan == 'DITERIMA')
|
||||||
@ -341,7 +328,6 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Progress bar untuk persentase bantuan yang diterima
|
|
||||||
if (totalBantuan > 0) ...[
|
if (totalBantuan > 0) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
@ -443,7 +429,6 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
decimalDigits: 0,
|
decimalDigits: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hitung total bantuan uang dan non-uang
|
|
||||||
double totalUang = 0;
|
double totalUang = 0;
|
||||||
Map<String, double> totalNonUang = {};
|
Map<String, double> totalNonUang = {};
|
||||||
|
|
||||||
@ -742,4 +727,146 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showBuatPengaduanDialog(BuildContext context) {
|
||||||
|
// Daftar penerimaan bantuan yang dapat diadukan (status DITERIMA)
|
||||||
|
final bantuanDiterima = controller.penerimaPenyaluran
|
||||||
|
.where((item) => item.statusPenerimaan == 'DITERIMA')
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Jika tidak ada bantuan yang diterima
|
||||||
|
if (bantuanDiterima.isEmpty) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Informasi',
|
||||||
|
'Tidak ada bantuan yang sudah diterima untuk dapat diajukan pengaduan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variabel untuk menyimpan pilihan penerimaan
|
||||||
|
PenerimaPenyaluranModel? selectedPenerimaan = bantuanDiterima.first;
|
||||||
|
|
||||||
|
Get.dialog(
|
||||||
|
Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.report_problem,
|
||||||
|
color: Colors.orange.shade700,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Text(
|
||||||
|
'Buat Pengaduan Baru',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text(
|
||||||
|
'Pilih Bantuan yang Ingin Diadukan:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
StatefulBuilder(
|
||||||
|
builder: (context, setState) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<PenerimaPenyaluranModel>(
|
||||||
|
isExpanded: true,
|
||||||
|
value: selectedPenerimaan,
|
||||||
|
items: bantuanDiterima.map((item) {
|
||||||
|
String displayText = item.namaPenyaluran ?? 'Bantuan';
|
||||||
|
if (item.tanggalPenerimaan != null) {
|
||||||
|
displayText +=
|
||||||
|
' (${DateFormat('dd/MM/yyyy').format(item.tanggalPenerimaan!)})';
|
||||||
|
}
|
||||||
|
|
||||||
|
return DropdownMenuItem<PenerimaPenyaluranModel>(
|
||||||
|
value: item,
|
||||||
|
child: Text(displayText),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
selectedPenerimaan = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (selectedPenerimaan != null) {
|
||||||
|
Get.back();
|
||||||
|
Get.to(
|
||||||
|
() => FormPengaduanView(
|
||||||
|
uidPenerimaan: selectedPenerimaan!.id.toString(),
|
||||||
|
),
|
||||||
|
transition: Transition.rightToLeft,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.orange.shade700,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Lanjutkan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1301,13 +1301,13 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
|
|||||||
|
|
||||||
Get.back(); // Tutup dialog terlebih dahulu
|
Get.back(); // Tutup dialog terlebih dahulu
|
||||||
|
|
||||||
// Tampilkan loading
|
// // Tampilkan loading
|
||||||
Get.dialog(
|
// Get.dialog(
|
||||||
const Center(
|
// const Center(
|
||||||
child: CircularProgressIndicator(),
|
// child: CircularProgressIndicator(),
|
||||||
),
|
// ),
|
||||||
barrierDismissible: false,
|
// barrierDismissible: false,
|
||||||
);
|
// );
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
try {
|
try {
|
||||||
@ -1336,18 +1336,6 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
|
|||||||
// Refresh data halaman
|
// Refresh data halaman
|
||||||
await controller.fetchPengaduan();
|
await controller.fetchPengaduan();
|
||||||
await controller.fetchPenerimaPenyaluran();
|
await controller.fetchPenerimaPenyaluran();
|
||||||
|
|
||||||
Get.snackbar(
|
|
||||||
'Sukses',
|
|
||||||
'Pengaduan berhasil dikirim dan data diperbarui',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
colorText: Colors.white,
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Navigate ke halaman pengaduan jika berhasil
|
|
||||||
controller.changeTab(2); // Tab pengaduan
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
@ -1,40 +1,48 @@
|
|||||||
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/data/models/penerima_penyaluran_model.dart';
|
||||||
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
|
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/warga/views/form_pengaduan_view.dart';
|
||||||
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
class WargaPengaduanView extends GetView<WargaDashboardController> {
|
class WargaPengaduanView extends GetView<WargaDashboardController> {
|
||||||
const WargaPengaduanView({super.key});
|
const WargaPengaduanView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(() {
|
return Scaffold(
|
||||||
if (controller.isLoading.value) {
|
body: Obx(() {
|
||||||
return const Center(child: CircularProgressIndicator());
|
if (controller.isLoading.value) {
|
||||||
}
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
// Debug print untuk melihat jumlah item
|
// Debug print untuk melihat jumlah item
|
||||||
print('DEBUG: Jumlah pengaduan tersedia: ${controller.pengaduan.length}');
|
print(
|
||||||
|
'DEBUG: Jumlah pengaduan tersedia: ${controller.pengaduan.length}');
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
// Tambahkan delay untuk memastikan refresh indicator terlihat
|
// Tambahkan delay untuk memastikan refresh indicator terlihat
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
controller.fetchData();
|
controller.fetchData();
|
||||||
},
|
},
|
||||||
child: controller.pengaduan.isEmpty
|
child: controller.pengaduan.isEmpty
|
||||||
? ListView(
|
? ListView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Get.height * 0.7,
|
height: Get.height * 0.7,
|
||||||
child: _buildEmptyState(),
|
child: _buildEmptyState(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: _buildPengaduanList(context),
|
: _buildPengaduanList(context),
|
||||||
);
|
);
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState() {
|
Widget _buildEmptyState() {
|
||||||
@ -89,8 +97,6 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final item = controller.pengaduan[index];
|
final item = controller.pengaduan[index];
|
||||||
print(
|
|
||||||
'DEBUG: Membangun item pengaduan $index dengan id: ${item.id}');
|
|
||||||
|
|
||||||
// Tentukan status dan warna berdasarkan status pengaduan
|
// Tentukan status dan warna berdasarkan status pengaduan
|
||||||
Color statusColor;
|
Color statusColor;
|
||||||
|
@ -16,16 +16,16 @@ class WargaView extends GetView<WargaDashboardController> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Tambahkan listener untuk refresh data saat fokus didapatkan kembali
|
// Tambahkan listener untuk refresh data saat fokus didapatkan kembali
|
||||||
// misalnya ketika kembali dari halaman profil
|
// misalnya ketika kembali dari halaman profil
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final focusNode = FocusNode();
|
// final focusNode = FocusNode();
|
||||||
FocusScope.of(context).requestFocus(focusNode);
|
// FocusScope.of(context).requestFocus(focusNode);
|
||||||
focusNode.addListener(() {
|
// focusNode.addListener(() {
|
||||||
if (focusNode.hasFocus) {
|
// if (focusNode.hasFocus) {
|
||||||
print('DEBUG WARGA: Halaman mendapatkan fokus, memuat ulang data');
|
// print('DEBUG WARGA: Halaman mendapatkan fokus, memuat ulang data');
|
||||||
controller.refreshData();
|
// controller.refreshData();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: scaffoldKey,
|
key: scaffoldKey,
|
||||||
|
@ -40,14 +40,11 @@ class SupabaseService extends GetxService {
|
|||||||
print('DEBUG: Auth state changed: $event');
|
print('DEBUG: Auth state changed: $event');
|
||||||
|
|
||||||
if (event == AuthChangeEvent.signedIn) {
|
if (event == AuthChangeEvent.signedIn) {
|
||||||
print('DEBUG: User signed in');
|
|
||||||
_isSessionInitialized = true;
|
_isSessionInitialized = true;
|
||||||
} else if (event == AuthChangeEvent.signedOut) {
|
} else if (event == AuthChangeEvent.signedOut) {
|
||||||
print('DEBUG: User signed out');
|
|
||||||
_cachedUserProfile = null;
|
_cachedUserProfile = null;
|
||||||
_isSessionInitialized = false;
|
_isSessionInitialized = false;
|
||||||
} else if (event == AuthChangeEvent.tokenRefreshed) {
|
} else if (event == AuthChangeEvent.tokenRefreshed) {
|
||||||
print('DEBUG: Token refreshed');
|
|
||||||
_isSessionInitialized = true;
|
_isSessionInitialized = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -55,7 +52,6 @@ class SupabaseService extends GetxService {
|
|||||||
// Periksa apakah ada sesi yang aktif
|
// Periksa apakah ada sesi yang aktif
|
||||||
final session = client.auth.currentSession;
|
final session = client.auth.currentSession;
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
print('DEBUG: Session aktif ditemukan saat inisialisasi');
|
|
||||||
_isSessionInitialized = true;
|
_isSessionInitialized = true;
|
||||||
} else {
|
} else {
|
||||||
print('DEBUG: Tidak ada session aktif saat inisialisasi');
|
print('DEBUG: Tidak ada session aktif saat inisialisasi');
|
||||||
@ -82,7 +78,6 @@ class SupabaseService extends GetxService {
|
|||||||
_cachedUserProfile = null; // Hapus cache saat logout
|
_cachedUserProfile = null; // Hapus cache saat logout
|
||||||
_isSessionInitialized = false;
|
_isSessionInitialized = false;
|
||||||
await client.auth.signOut();
|
await client.auth.signOut();
|
||||||
print('DEBUG: Logout berhasil, sesi dihapus');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan user saat ini
|
// Metode untuk mendapatkan user saat ini
|
||||||
@ -99,10 +94,8 @@ class SupabaseService extends GetxService {
|
|||||||
final isValid = session.expiresAt != null && session.expiresAt! > now;
|
final isValid = session.expiresAt != null && session.expiresAt! > now;
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
print('DEBUG: Sesi valid, user terautentikasi');
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
print('DEBUG: Sesi kedaluwarsa, user tidak terautentikasi');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -480,6 +473,51 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan jumlah penyaluran berdasarkan status
|
||||||
|
Future<Map<String, int>?> getStatusPenyaluran() async {
|
||||||
|
try {
|
||||||
|
final result = {
|
||||||
|
'dijadwalkan': 0,
|
||||||
|
'aktif': 0,
|
||||||
|
'batal': 0,
|
||||||
|
'terlaksana': 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mendapatkan jumlah penyaluran dengan status DIJADWALKAN
|
||||||
|
final dijadwalkanResponse = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('id')
|
||||||
|
.eq('status', 'DIJADWALKAN');
|
||||||
|
result['dijadwalkan'] = dijadwalkanResponse.length;
|
||||||
|
|
||||||
|
// Mendapatkan jumlah penyaluran dengan status AKTIF
|
||||||
|
final aktifResponse = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('id')
|
||||||
|
.eq('status', 'AKTIF');
|
||||||
|
result['aktif'] = aktifResponse.length;
|
||||||
|
|
||||||
|
// Mendapatkan jumlah penyaluran dengan status BATAL
|
||||||
|
final batalResponse = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('id')
|
||||||
|
.eq('status', 'BATALTERLAKSANA');
|
||||||
|
result['batal'] = batalResponse.length;
|
||||||
|
|
||||||
|
// Mendapatkan jumlah penyaluran dengan status TERLAKSANA
|
||||||
|
final terlaksanaResponse = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('id')
|
||||||
|
.eq('status', 'TERLAKSANA');
|
||||||
|
result['terlaksana'] = terlaksanaResponse.length;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting status penyaluran: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>?> getNotifikasiBelumDibaca(
|
Future<List<Map<String, dynamic>>?> getNotifikasiBelumDibaca(
|
||||||
String userId) async {
|
String userId) async {
|
||||||
try {
|
try {
|
||||||
@ -499,16 +537,8 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Jadwal penyaluran methods
|
// Jadwal penyaluran methods
|
||||||
Future<List<Map<String, dynamic>>?> getJadwalHariIni() async {
|
Future<List<Map<String, dynamic>>?> getJadwalAktif() async {
|
||||||
try {
|
try {
|
||||||
final now = DateTime.now();
|
|
||||||
final today = DateTime(now.year, now.month, now.day);
|
|
||||||
final tomorrow = today.add(const Duration(days: 1));
|
|
||||||
|
|
||||||
// Konversi ke UTC untuk query ke database
|
|
||||||
final todayUtc = today.toUtc().toIso8601String();
|
|
||||||
final tomorrowUtc = tomorrow.toUtc().toIso8601String();
|
|
||||||
|
|
||||||
final response = await client
|
final response = await client
|
||||||
.from('penyaluran_bantuan')
|
.from('penyaluran_bantuan')
|
||||||
.select('''
|
.select('''
|
||||||
@ -518,15 +548,12 @@ class SupabaseService extends GetxService {
|
|||||||
id, nama, alamat_lengkap
|
id, nama, alamat_lengkap
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
.gte('tanggal_penyaluran', todayUtc)
|
.eq('status', 'AKTIF')
|
||||||
.lt('tanggal_penyaluran', tomorrowUtc)
|
.order('tanggal_penyaluran', ascending: true);
|
||||||
.inFilter('status', ['AKTIF', 'DIJADWALKAN']);
|
|
||||||
|
|
||||||
print("hari ini $response");
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getting jadwal hari ini: $e');
|
print('Error getting jadwal aktif: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1125,8 +1152,7 @@ class SupabaseService extends GetxService {
|
|||||||
.from('tindakan_pengaduan')
|
.from('tindakan_pengaduan')
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
petugas:petugas_id(id, nama_lengkap, nip),
|
petugas:petugas_id(id, nama_lengkap, nip)
|
||||||
verifikator:verifikator_id(id, nama_lengkap, nip)
|
|
||||||
''')
|
''')
|
||||||
.eq('pengaduan_id', pengaduanId)
|
.eq('pengaduan_id', pengaduanId)
|
||||||
.order('created_at', ascending: false);
|
.order('created_at', ascending: false);
|
||||||
@ -1437,7 +1463,6 @@ class SupabaseService extends GetxService {
|
|||||||
try {
|
try {
|
||||||
// Buat map untuk update data
|
// Buat map untuk update data
|
||||||
final Map<String, dynamic> updateData = {
|
final Map<String, dynamic> updateData = {
|
||||||
'nama': nama,
|
|
||||||
'nama_lengkap': nama, // Untuk konsistensi dengan field nama_lengkap
|
'nama_lengkap': nama, // Untuk konsistensi dengan field nama_lengkap
|
||||||
'no_hp': noHp,
|
'no_hp': noHp,
|
||||||
'updated_at': DateTime.now().toIso8601String(),
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
@ -1610,68 +1635,68 @@ class SupabaseService extends GetxService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui status penerimaan bantuan
|
// // Metode untuk memperbarui status penerimaan bantuan
|
||||||
Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
// Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
||||||
{DateTime? tanggalPenerimaan,
|
// {DateTime? tanggalPenerimaan,
|
||||||
String? buktiPenerimaan,
|
// String? buktiPenerimaan,
|
||||||
String? keterangan}) async {
|
// String? keterangan}) async {
|
||||||
try {
|
// try {
|
||||||
// Periksa petugas ID
|
// // Periksa petugas ID
|
||||||
final petugasId = client.auth.currentUser?.id;
|
// final petugasId = client.auth.currentUser?.id;
|
||||||
if (petugasId == null) {
|
// if (petugasId == null) {
|
||||||
throw Exception('ID petugas tidak ditemukan');
|
// throw Exception('ID petugas tidak ditemukan');
|
||||||
}
|
// }
|
||||||
|
|
||||||
final Map<String, dynamic> updateData = {
|
// final Map<String, dynamic> updateData = {
|
||||||
'status_penerimaan': status,
|
// 'status_penerimaan': status,
|
||||||
};
|
// };
|
||||||
|
|
||||||
if (tanggalPenerimaan != null) {
|
// if (tanggalPenerimaan != null) {
|
||||||
updateData['tanggal_penerimaan'] = tanggalPenerimaan.toIso8601String();
|
// updateData['tanggal_penerimaan'] = tanggalPenerimaan.toIso8601String();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (buktiPenerimaan != null) {
|
// if (buktiPenerimaan != null) {
|
||||||
updateData['bukti_penerimaan'] = buktiPenerimaan;
|
// updateData['bukti_penerimaan'] = buktiPenerimaan;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (keterangan != null) {
|
// if (keterangan != null) {
|
||||||
updateData['keterangan'] = keterangan;
|
// updateData['keterangan'] = keterangan;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Update status penerimaan
|
// // Update status penerimaan
|
||||||
await client
|
// await client
|
||||||
.from('penerima_penyaluran')
|
// .from('penerima_penyaluran')
|
||||||
.update(updateData)
|
// .update(updateData)
|
||||||
.eq('id', penerimaId);
|
// .eq('id', penerimaId);
|
||||||
|
|
||||||
// Jika status adalah DITERIMA, kurangi stok
|
// // Jika status adalah DITERIMA, kurangi stok
|
||||||
if (status.toUpperCase() == 'DITERIMA') {
|
// if (status.toUpperCase() == 'DITERIMA') {
|
||||||
// Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah)
|
// // Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah)
|
||||||
final penerimaData = await client
|
// final penerimaData = await client
|
||||||
.from('penerima_penyaluran')
|
// .from('penerima_penyaluran')
|
||||||
.select('penyaluran_bantuan_id, stok_bantuan_id, jumlah')
|
// .select('penyaluran_bantuan_id, stok_bantuan_id, jumlah')
|
||||||
.eq('id', penerimaId)
|
// .eq('id', penerimaId)
|
||||||
.single();
|
// .single();
|
||||||
|
|
||||||
if (penerimaData != null) {
|
// if (penerimaData != null) {
|
||||||
final String penyaluranId = penerimaData['penyaluran_bantuan_id'];
|
// final String penyaluranId = penerimaData['penyaluran_bantuan_id'];
|
||||||
final String stokBantuanId = penerimaData['stok_bantuan_id'];
|
// final String stokBantuanId = penerimaData['stok_bantuan_id'];
|
||||||
final double jumlah = penerimaData['jumlah'] is int
|
// final double jumlah = penerimaData['jumlah'] is int
|
||||||
? penerimaData['jumlah'].toDouble()
|
// ? penerimaData['jumlah'].toDouble()
|
||||||
: penerimaData['jumlah'];
|
// : penerimaData['jumlah'];
|
||||||
|
|
||||||
// Kurangi stok dan catat riwayat
|
// // Kurangi stok dan catat riwayat
|
||||||
await kurangiStokDariPenyaluran(
|
// await kurangiStokDariPenyaluran(
|
||||||
penyaluranId, stokBantuanId, jumlah, petugasId);
|
// penyaluranId, stokBantuanId, jumlah, petugasId);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return true;
|
// return true;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print('Error updating status penerimaan: $e');
|
// print('Error updating status penerimaan: $e');
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Metode untuk mendapatkan semua kategori bantuan
|
// Metode untuk mendapatkan semua kategori bantuan
|
||||||
Future<List<Map<String, dynamic>>?> getAllKategoriBantuan() async {
|
Future<List<Map<String, dynamic>>?> getAllKategoriBantuan() async {
|
||||||
@ -2024,7 +2049,7 @@ class SupabaseService extends GetxService {
|
|||||||
'stok_bantuan_id': stokBantuanId,
|
'stok_bantuan_id': stokBantuanId,
|
||||||
'jenis_perubahan': 'pengurangan',
|
'jenis_perubahan': 'pengurangan',
|
||||||
'jumlah': jumlah,
|
'jumlah': jumlah,
|
||||||
'sumber': 'penyaluran',
|
'sumber': 'penerimaan',
|
||||||
'id_referensi': penyaluranId,
|
'id_referensi': penyaluranId,
|
||||||
'created_by_id': petugasId,
|
'created_by_id': petugasId,
|
||||||
'created_at': DateTime.now().toIso8601String()
|
'created_at': DateTime.now().toIso8601String()
|
||||||
@ -2176,4 +2201,36 @@ class SupabaseService extends GetxService {
|
|||||||
throw e; // Re-throw untuk penanganan di tingkat yang lebih tinggi
|
throw e; // Re-throw untuk penanganan di tingkat yang lebih tinggi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tambahkan metode untuk mendapatkan data penitipan berdasarkan ID
|
||||||
|
Future<Map<String, dynamic>?> getPenitipanById(String id) async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('penitipan_bantuan').select('''
|
||||||
|
*,
|
||||||
|
donatur:donatur_id(*),
|
||||||
|
petugas_desa:petugas_desa_id(*)
|
||||||
|
''').eq('id', id).single();
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting penitipan by id: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan metode untuk mendapatkan data penerimaan berdasarkan ID
|
||||||
|
Future<Map<String, dynamic>?> getPenerimaanById(String id) async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('penerima_penyaluran').select('''
|
||||||
|
*,
|
||||||
|
warga:warga_id(*),
|
||||||
|
penyaluran_bantuan:penyaluran_bantuan_id(*,
|
||||||
|
petugas_desa:petugas_id(*)
|
||||||
|
)
|
||||||
|
''').eq('id', id).single();
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting penerimaan by id: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user