Tambahkan dependensi baru timeline_tile versi 2.0.0 ke dalam pubspec.yaml dan pubspec.lock. Perbarui model PengaduanModel dan TindakanPengaduanModel untuk mendukung struktur data yang lebih kompleks, termasuk penambahan properti baru. Modifikasi PengaduanController untuk menggunakan metode baru dalam mengambil data pengaduan dengan detail penerima penyaluran. Perbarui tampilan di PengaduanView untuk meningkatkan pengalaman pengguna dengan menampilkan informasi penyaluran bantuan yang lebih lengkap.

This commit is contained in:
Khafidh Fuadi
2025-03-16 22:15:45 +07:00
parent 76b167c65c
commit c9587758c6
20 changed files with 3572 additions and 368 deletions

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> ?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
 
}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2~ }D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2~
| |
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD> <EFBFBD>
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p
n n
lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t
r r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2y pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2y
w w
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2z 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|

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> ?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD> <EFBFBD>
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
~ ~
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> |D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD> <EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2r <EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2r
p p
nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2v nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2v
t t
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2{ rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2{
y y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2| 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~

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{ ?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{
y y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2x wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2x
v v
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2} tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2}
{ {
yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  ů<EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j
h h
fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  ů<EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n
l l
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  ů<EFBFBD><EFBFBD><EFBFBD>2s jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2s
q q
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  ů<EFBFBD><EFBFBD><EFBFBD>2 oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  <EFBFBD><EFBFBD><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

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ̱<EFBFBD><EFBFBD><EFBFBD>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><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2{ zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  <EFBFBD><EFBFBD><EFBFBD><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><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  <EFBFBD><EFBFBD><EFBFBD><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><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2m |D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  <EFBFBD><EFBFBD><EFBFBD><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><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2q iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  <EFBFBD><EFBFBD><EFBFBD><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><EFBFBD><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><EFBFBD><EFBFBD>2 rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  <EFBFBD><EFBFBD><EFBFBD><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

View File

@ -5,26 +5,28 @@ class PengaduanModel {
final String? judul; final String? judul;
final String? deskripsi; final String? deskripsi;
final String? status; final String? status;
final String? kategori; final String? wargaId;
final String? pelapor; final List<dynamic>? fotoPengaduan;
final String? kontakPelapor; final String? penerimaPenyaluranId;
final List<String>? gambarUrls;
final DateTime? tanggalPengaduan; final DateTime? tanggalPengaduan;
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
final Map<String, dynamic>? penerimaPenyaluran;
final Map<String, dynamic>? warga;
PengaduanModel({ PengaduanModel({
this.id, this.id,
this.judul, this.judul,
this.deskripsi, this.deskripsi,
this.status, this.status,
this.kategori, this.wargaId,
this.pelapor, this.fotoPengaduan,
this.kontakPelapor, this.penerimaPenyaluranId,
this.gambarUrls,
this.tanggalPengaduan, this.tanggalPengaduan,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
this.penerimaPenyaluran,
this.warga,
}); });
factory PengaduanModel.fromRawJson(String str) => factory PengaduanModel.fromRawJson(String str) =>
@ -37,12 +39,9 @@ class PengaduanModel {
judul: json["judul"], judul: json["judul"],
deskripsi: json["deskripsi"], deskripsi: json["deskripsi"],
status: json["status"], status: json["status"],
kategori: json["kategori"], wargaId: json["warga_id"],
pelapor: json["pelapor"], fotoPengaduan: json["foto_pengaduan"],
kontakPelapor: json["kontak_pelapor"], penerimaPenyaluranId: json["penerima_penyaluran_id"],
gambarUrls: json["gambar_urls"] == null
? null
: List<String>.from(json["gambar_urls"].map((x) => x)),
tanggalPengaduan: json["tanggal_pengaduan"] != null tanggalPengaduan: json["tanggal_pengaduan"] != null
? DateTime.parse(json["tanggal_pengaduan"]) ? DateTime.parse(json["tanggal_pengaduan"])
: null, : null,
@ -52,6 +51,8 @@ class PengaduanModel {
updatedAt: json["updated_at"] != null updatedAt: json["updated_at"] != null
? DateTime.parse(json["updated_at"]) ? DateTime.parse(json["updated_at"])
: null, : null,
penerimaPenyaluran: json["penerima_penyaluran"],
warga: json["warga"],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -59,14 +60,47 @@ class PengaduanModel {
"judul": judul, "judul": judul,
"deskripsi": deskripsi, "deskripsi": deskripsi,
"status": status, "status": status,
"kategori": kategori, "warga_id": wargaId,
"pelapor": pelapor, "foto_pengaduan": fotoPengaduan,
"kontak_pelapor": kontakPelapor, "penerima_penyaluran_id": penerimaPenyaluranId,
"gambar_urls": gambarUrls == null
? null
: List<dynamic>.from(gambarUrls!.map((x) => x)),
"tanggal_pengaduan": tanggalPengaduan?.toIso8601String(), "tanggal_pengaduan": tanggalPengaduan?.toIso8601String(),
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
"penerima_penyaluran": penerimaPenyaluran,
"warga": warga,
}; };
// Getter untuk mendapatkan informasi penyaluran bantuan
Map<String, dynamic>? get penyaluranBantuan {
return penerimaPenyaluran?['penyaluran_bantuan'];
}
// Getter untuk mendapatkan informasi stok bantuan
Map<String, dynamic>? get stokBantuan {
return penerimaPenyaluran?['stok_bantuan'];
}
// Getter untuk mendapatkan nama penyaluran
String get namaPenyaluran {
return penyaluranBantuan?['nama'] ?? 'Tidak ada data';
}
// Getter untuk mendapatkan deskripsi penyaluran
String get deskripsiPenyaluran {
return penyaluranBantuan?['deskripsi'] ?? 'Tidak ada deskripsi';
}
// Getter untuk mendapatkan jenis bantuan
String get jenisBantuan {
return stokBantuan?['kategori_bantuan']?['nama'] ?? 'Tidak diketahui';
}
// Getter untuk mendapatkan jumlah bantuan yang diterima
String get jumlahBantuan {
final jumlah = penerimaPenyaluran?['jumlah_bantuan'];
final satuan = penerimaPenyaluran?['satuan'] ?? '';
if (jumlah == null) return 'Tidak diketahui';
return '$jumlah $satuan';
}
} }

View File

@ -5,22 +5,46 @@ class TindakanPengaduanModel {
final String? pengaduanId; final String? pengaduanId;
final String? tindakan; final String? tindakan;
final String? catatan; final String? catatan;
final String? status; final String? statusTindakan; // PROSES, SELESAI
final String? prioritas; // RENDAH, SEDANG, TINGGI
final String? kategoriTindakan; // Kategori tindakan enum
final String? petugasId; final String? petugasId;
final String? verifikatorId;
final String? hasilTindakan;
final List<dynamic>? buktiTindakan;
final DateTime? estimasiSelesai;
final DateTime? tanggalTindakan; final DateTime? tanggalTindakan;
final DateTime? tanggalVerifikasi;
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
final double? biayaTindakan;
final String? feedbackWarga;
final int? ratingWarga;
final Map<String, dynamic>? petugas; // Data petugas yang melakukan tindakan
final Map<String, dynamic>? verifikator; // Data petugas yang memverifikasi
TindakanPengaduanModel({ TindakanPengaduanModel({
this.id, this.id,
this.pengaduanId, this.pengaduanId,
this.tindakan, this.tindakan,
this.catatan, this.catatan,
this.status, this.statusTindakan,
this.prioritas,
this.kategoriTindakan,
this.petugasId, this.petugasId,
this.verifikatorId,
this.hasilTindakan,
this.buktiTindakan,
this.estimasiSelesai,
this.tanggalTindakan, this.tanggalTindakan,
this.tanggalVerifikasi,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
this.biayaTindakan,
this.feedbackWarga,
this.ratingWarga,
this.petugas,
this.verifikator,
}); });
factory TindakanPengaduanModel.fromRawJson(String str) => factory TindakanPengaduanModel.fromRawJson(String str) =>
@ -34,17 +58,35 @@ class TindakanPengaduanModel {
pengaduanId: json["pengaduan_id"], pengaduanId: json["pengaduan_id"],
tindakan: json["tindakan"], tindakan: json["tindakan"],
catatan: json["catatan"], catatan: json["catatan"],
status: json["status"], statusTindakan: json["status_tindakan"],
prioritas: json["prioritas"],
kategoriTindakan: json["kategori_tindakan"],
petugasId: json["petugas_id"], petugasId: json["petugas_id"],
verifikatorId: json["verifikator_id"],
hasilTindakan: json["hasil_tindakan"],
buktiTindakan: json["bukti_tindakan"],
estimasiSelesai: json["estimasi_selesai"] != null
? DateTime.parse(json["estimasi_selesai"])
: null,
tanggalTindakan: json["tanggal_tindakan"] != null tanggalTindakan: json["tanggal_tindakan"] != null
? DateTime.parse(json["tanggal_tindakan"]) ? DateTime.parse(json["tanggal_tindakan"])
: null, : null,
tanggalVerifikasi: json["tanggal_verifikasi"] != null
? DateTime.parse(json["tanggal_verifikasi"])
: null,
createdAt: json["created_at"] != null createdAt: json["created_at"] != null
? DateTime.parse(json["created_at"]) ? DateTime.parse(json["created_at"])
: null, : null,
updatedAt: json["updated_at"] != null updatedAt: json["updated_at"] != null
? DateTime.parse(json["updated_at"]) ? DateTime.parse(json["updated_at"])
: null, : null,
biayaTindakan: json["biaya_tindakan"] != null
? double.parse(json["biaya_tindakan"].toString())
: null,
feedbackWarga: json["feedback_warga"],
ratingWarga: json["rating_warga"],
petugas: json["petugas"],
verifikator: json["verifikator"],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -52,10 +94,79 @@ class TindakanPengaduanModel {
"pengaduan_id": pengaduanId, "pengaduan_id": pengaduanId,
"tindakan": tindakan, "tindakan": tindakan,
"catatan": catatan, "catatan": catatan,
"status": status, "status_tindakan": statusTindakan,
"prioritas": prioritas,
"kategori_tindakan": kategoriTindakan,
"petugas_id": petugasId, "petugas_id": petugasId,
"verifikator_id": verifikatorId,
"hasil_tindakan": hasilTindakan,
"bukti_tindakan": buktiTindakan,
"estimasi_selesai": estimasiSelesai?.toIso8601String(),
"tanggal_tindakan": tanggalTindakan?.toIso8601String(), "tanggal_tindakan": tanggalTindakan?.toIso8601String(),
"tanggal_verifikasi": tanggalVerifikasi?.toIso8601String(),
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
"biaya_tindakan": biayaTindakan,
"feedback_warga": feedbackWarga,
"rating_warga": ratingWarga,
"petugas": petugas,
"verifikator": verifikator,
}; };
// Getter untuk mendapatkan nama petugas
String get namaPetugas {
if (petugas != null && petugas!['nama'] != null) {
return petugas!['nama'];
} else if (petugas != null && petugas!['name'] != null) {
return petugas!['name'];
}
return 'Petugas';
}
// Getter untuk mendapatkan nama verifikator
String get namaVerifikator {
if (verifikator != null && verifikator!['nama'] != null) {
return verifikator!['nama'];
} else if (verifikator != null && verifikator!['name'] != null) {
return verifikator!['name'];
}
return 'Belum diverifikasi';
}
// Getter untuk mendapatkan status tindakan yang lebih user-friendly
String get statusTindakanText {
switch (statusTindakan) {
case 'PROSES':
return 'Dalam Proses';
case 'SELESAI':
return 'Selesai';
default:
return statusTindakan ?? 'Tidak Diketahui';
}
}
// Getter untuk mendapatkan prioritas yang lebih user-friendly
String get prioritasText {
switch (prioritas) {
case 'RENDAH':
return 'Prioritas Rendah';
case 'SEDANG':
return 'Prioritas Sedang';
case 'TINGGI':
return 'Prioritas Tinggi';
default:
return prioritas ?? 'Tidak Diketahui';
}
}
// Getter untuk mendapatkan kategori tindakan yang lebih user-friendly
String get kategoriTindakanText {
if (kategoriTindakan == null) return 'Tidak Diketahui';
// Mengubah format SNAKE_CASE menjadi Title Case
return kategoriTindakan!
.split('_')
.map((word) => word[0].toUpperCase() + word.substring(1).toLowerCase())
.join(' ');
}
} }

View File

@ -0,0 +1,11 @@
import 'package:get/get.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/pengaduan_controller.dart';
class PengaduanBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<PengaduanController>(
() => PengaduanController(),
);
}
}

View File

@ -48,14 +48,15 @@ class PengaduanController extends GetxController {
Future<void> loadPengaduanData() async { Future<void> loadPengaduanData() async {
isLoading.value = true; isLoading.value = true;
try { try {
final pengaduanData = await _supabaseService.getPengaduan(); final pengaduanData =
await _supabaseService.getPengaduanWithPenerimaPenyaluran();
if (pengaduanData != null) { if (pengaduanData != null) {
daftarPengaduan.value = daftarPengaduan.value =
pengaduanData.map((data) => PengaduanModel.fromJson(data)).toList(); pengaduanData.map((data) => PengaduanModel.fromJson(data)).toList();
// Hitung jumlah berdasarkan status // Hitung jumlah berdasarkan status
jumlahDiproses.value = jumlahDiproses.value =
daftarPengaduan.where((item) => item.status == 'DIPROSES').length; daftarPengaduan.where((item) => item.status == 'MENUNGGU').length;
jumlahTindakan.value = jumlahTindakan.value =
daftarPengaduan.where((item) => item.status == 'TINDAKAN').length; daftarPengaduan.where((item) => item.status == 'TINDAKAN').length;
jumlahSelesai.value = jumlahSelesai.value =
@ -138,6 +139,36 @@ class PengaduanController extends GetxController {
} }
} }
Future<void> updateTindakan(
String tindakanId, Map<String, dynamic> data) async {
isLoading.value = true;
try {
await _supabaseService.updateTindakanPengaduan(tindakanId, data);
Get.snackbar(
'Sukses',
'Tindakan berhasil diperbarui',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
// Refresh data
Get.forceAppUpdate();
} catch (e) {
print('Error updating tindakan: $e');
Get.snackbar(
'Error',
'Gagal memperbarui tindakan: ${e.toString()}',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
}
}
Future<void> selesaikanPengaduan(String pengaduanId) async { Future<void> selesaikanPengaduan(String pengaduanId) async {
isLoading.value = true; isLoading.value = true;
try { try {
@ -164,6 +195,25 @@ class PengaduanController extends GetxController {
} }
} }
Future<void> updateStatusPengaduan(String pengaduanId, String status) async {
isLoading.value = true;
try {
await _supabaseService.updateStatusPengaduan(pengaduanId, status);
await loadPengaduanData();
} catch (e) {
print('Error updating pengaduan status: $e');
Get.snackbar(
'Error',
'Gagal mengubah status pengaduan: ${e.toString()}',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
}
}
Future<List<TindakanPengaduanModel>> getTindakanPengaduan( Future<List<TindakanPengaduanModel>> getTindakanPengaduan(
String pengaduanId) async { String pengaduanId) async {
try { try {
@ -200,7 +250,7 @@ class PengaduanController extends GetxController {
return daftarPengaduan; return daftarPengaduan;
case 1: case 1:
return daftarPengaduan return daftarPengaduan
.where((item) => item.status == 'DIPROSES') .where((item) => item.status == 'MENUNGGU')
.toList(); .toList();
case 2: case 2:
return daftarPengaduan return daftarPengaduan
@ -222,4 +272,40 @@ class PengaduanController extends GetxController {
} }
return null; return null;
} }
Future<Map<String, dynamic>> getDetailPengaduan(String pengaduanId) async {
try {
// Ambil data pengaduan
final pengaduanData =
await _supabaseService.client.from('pengaduan').select('''
*,
penerima_penyaluran:penerima_penyaluran_id(
*,
penyaluran_bantuan:penyaluran_bantuan_id(*),
stok_bantuan:stok_bantuan_id(*),
warga:warga_id(*)
),
warga:warga_id(*)
''').eq('id', pengaduanId).single();
// Ambil data tindakan pengaduan
final tindakanData =
await _supabaseService.getTindakanPengaduan(pengaduanId);
print(tindakanData);
// Gabungkan data
final result = {
'pengaduan': pengaduanData,
'tindakan': tindakanData ?? [],
};
return result;
} catch (e) {
print('Error getting detail pengaduan: $e');
return {
'pengaduan': null,
'tindakan': [],
};
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/pengaduan_controller.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart'; import 'package:penyaluran_app/app/theme/app_theme.dart';
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
class PengaduanView extends GetView<PetugasDesaController> { class PengaduanView extends GetView<PengaduanController> {
const PengaduanView({super.key}); const PengaduanView({super.key});
@override @override
@ -33,6 +34,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
} }
Widget _buildPengaduanSummary(BuildContext context) { Widget _buildPengaduanSummary(BuildContext context) {
return Obx(() {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -58,7 +60,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
context, context,
icon: Icons.pending_actions, icon: Icons.pending_actions,
title: 'Diproses', title: 'Diproses',
value: '3', value: controller.jumlahDiproses.toString(),
color: Colors.orange, color: Colors.orange,
), ),
), ),
@ -67,7 +69,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
context, context,
icon: Icons.engineering, icon: Icons.engineering,
title: 'Tindakan', title: 'Tindakan',
value: '2', value: controller.jumlahTindakan.toString(),
color: Colors.blue, color: Colors.blue,
), ),
), ),
@ -76,7 +78,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
context, context,
icon: Icons.check_circle, icon: Icons.check_circle,
title: 'Selesai', title: 'Selesai',
value: '8', value: controller.jumlahSelesai.toString(),
color: Colors.green, color: Colors.green,
), ),
), ),
@ -85,6 +87,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
], ],
), ),
); );
});
} }
Widget _buildSummaryItem( Widget _buildSummaryItem(
@ -133,6 +136,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
controller: controller.searchController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Cari pengaduan...', hintText: 'Cari pengaduan...',
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(Icons.search),
@ -144,6 +148,10 @@ class PengaduanView extends GetView<PetugasDesaController> {
fillColor: Colors.grey.shade100, fillColor: Colors.grey.shade100,
contentPadding: const EdgeInsets.symmetric(vertical: 0), contentPadding: const EdgeInsets.symmetric(vertical: 0),
), ),
onChanged: (value) {
// Implementasi pencarian
controller.refreshData();
},
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@ -173,21 +181,42 @@ class PengaduanView extends GetView<PetugasDesaController> {
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
CheckboxListTile( Obx(() => RadioListTile<int>(
title: const Text('Semua'),
value: 0,
groupValue: controller.selectedCategoryIndex.value,
onChanged: (value) {
controller.changeCategory(value!);
Navigator.pop(context);
},
)),
Obx(() => RadioListTile<int>(
title: const Text('Diproses'), title: const Text('Diproses'),
value: true, value: 1,
onChanged: (value) {}, groupValue: controller.selectedCategoryIndex.value,
), onChanged: (value) {
CheckboxListTile( controller.changeCategory(value!);
Navigator.pop(context);
},
)),
Obx(() => RadioListTile<int>(
title: const Text('Tindakan'), title: const Text('Tindakan'),
value: true, value: 2,
onChanged: (value) {}, groupValue: controller.selectedCategoryIndex.value,
), onChanged: (value) {
CheckboxListTile( controller.changeCategory(value!);
Navigator.pop(context);
},
)),
Obx(() => RadioListTile<int>(
title: const Text('Selesai'), title: const Text('Selesai'),
value: true, value: 3,
onChanged: (value) {}, groupValue: controller.selectedCategoryIndex.value,
), onChanged: (value) {
controller.changeCategory(value!);
Navigator.pop(context);
},
)),
], ],
), ),
actions: [ actions: [
@ -195,61 +224,53 @@ class PengaduanView extends GetView<PetugasDesaController> {
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: const Text('Batal'), child: const Text('Batal'),
), ),
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Terapkan'),
),
], ],
), ),
); );
} }
Widget _buildPengaduanList(BuildContext context) { Widget _buildPengaduanList(BuildContext context) {
final List<Map<String, dynamic>> pengaduanList = [ return Obx(() {
{ if (controller.isLoading.value) {
'id': '1', return const Center(
'nama': 'Budi Santoso', child: Padding(
'nik': '3201020107030011', padding: EdgeInsets.all(20.0),
'jenis_pengaduan': 'Bantuan Tidak Diterima', child: CircularProgressIndicator(),
'deskripsi': ),
'Saya belum menerima bantuan beras yang dijadwalkan minggu lalu', );
'tanggal': '15 April 2023', }
'status': 'Diproses',
}, final filteredPengaduan = controller.getFilteredPengaduan();
{
'id': '2', if (filteredPengaduan.isEmpty) {
'nama': 'Siti Rahayu', return Center(
'nik': '3201020107030010', child: Padding(
'jenis_pengaduan': 'Kualitas Bantuan', padding: const EdgeInsets.all(20.0),
'deskripsi': child: Column(
'Beras yang diterima berkualitas buruk dan tidak layak konsumsi', children: [
'tanggal': '14 April 2023', const Icon(
'status': 'Tindakan', Icons.inbox_outlined,
}, size: 80,
{ color: Colors.grey,
'id': '3', ),
'nama': 'Ahmad Fauzi', const SizedBox(height: 16),
'nik': '3201020107030013', Text(
'jenis_pengaduan': 'Jumlah Bantuan', 'Belum ada pengaduan',
'deskripsi': style: Theme.of(context).textTheme.titleMedium?.copyWith(
'Jumlah bantuan yang diterima tidak sesuai dengan yang dijanjikan', color: Colors.grey,
'tanggal': '13 April 2023', ),
'status': 'Tindakan', ),
}, ],
{ ),
'id': '4', ),
'nama': 'Dewi Lestari', );
'nik': '3201020107030012', }
'jenis_pengaduan': 'Jadwal Penyaluran',
'deskripsi':
'Jadwal penyaluran bantuan sering berubah tanpa pemberitahuan',
'tanggal': '10 April 2023',
'status': 'Selesai',
},
];
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Daftar Pengaduan', 'Daftar Pengaduan',
@ -257,22 +278,31 @@ class PengaduanView extends GetView<PetugasDesaController> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => controller.refreshData(),
tooltip: 'Refresh',
),
],
),
const SizedBox(height: 12), const SizedBox(height: 12),
...pengaduanList.map((item) => _buildPengaduanItem(context, item)), ...filteredPengaduan
.map((item) => _buildPengaduanItem(context, item)),
], ],
); );
});
} }
Widget _buildPengaduanItem(BuildContext context, Map<String, dynamic> item) { Widget _buildPengaduanItem(BuildContext context, dynamic item) {
Color statusColor; Color statusColor;
IconData statusIcon; IconData statusIcon;
switch (item['status']) { switch (item.status?.toUpperCase()) {
case 'MENUNGGU': case 'MENUNGGU':
statusColor = AppTheme.warningColor; statusColor = AppTheme.warningColor;
statusIcon = Icons.pending; statusIcon = Icons.pending;
break; break;
case 'DIPROSES': case 'TINDAKAN':
statusColor = AppTheme.infoColor; statusColor = AppTheme.infoColor;
statusIcon = Icons.sync; statusIcon = Icons.sync;
break; break;
@ -285,6 +315,14 @@ class PengaduanView extends GetView<PetugasDesaController> {
statusIcon = Icons.help_outline; statusIcon = Icons.help_outline;
} }
// Format tanggal menggunakan DateTimeHelper
String formattedDate = '';
if (item.tanggalPengaduan != null) {
formattedDate = DateTimeHelper.formatDate(item.tanggalPengaduan);
} else if (item.createdAt != null) {
formattedDate = DateTimeHelper.formatDate(item.createdAt);
}
return Container( return Container(
width: double.infinity, width: double.infinity,
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
@ -310,7 +348,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
item['nama'] ?? '', item.warga?['nama'] ?? item.judul ?? '',
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -334,7 +372,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
item['status'] ?? '', item.status ?? '',
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: statusColor, color: statusColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -347,12 +385,13 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'NIK: ${item['nik'] ?? ''}', 'NIK: ${item.warga?['nik'] ?? ''}',
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey, color: Colors.grey,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
if (item.penerimaPenyaluran != null) ...[
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -360,15 +399,50 @@ class PengaduanView extends GetView<PetugasDesaController> {
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: Text( child: Text(
item['jenis_pengaduan'] ?? '', 'Penyaluran: ${item.namaPenyaluran}',
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Jenis: ${item.jenisBantuan}',
style: Theme.of(context).textTheme.bodySmall,
),
),
),
const SizedBox(width: 8),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Jumlah: ${item.jumlahBantuan}',
style: Theme.of(context).textTheme.bodySmall,
),
),
),
],
),
],
const SizedBox(height: 8),
Text( Text(
item['deskripsi'] ?? '', item.deskripsi ?? '',
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -383,7 +457,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
item['tanggal'] ?? '', formattedDate,
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey, color: Colors.grey,
), ),
@ -401,11 +475,10 @@ class PengaduanView extends GetView<PetugasDesaController> {
); );
} }
List<Widget> _buildActionButtons( List<Widget> _buildActionButtons(BuildContext context, dynamic item) {
BuildContext context, Map<String, dynamic> item) { final status = item.status?.toUpperCase();
final status = item['status'];
if (status == 'Diproses') { if (status == 'MENUNGGU') {
return [ return [
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
@ -421,8 +494,8 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// Implementasi untuk melihat detail pengaduan // Navigasi ke halaman detail pengaduan
_showDetailDialog(context, item); Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
}, },
icon: const Icon(Icons.info_outline, size: 18), icon: const Icon(Icons.info_outline, size: 18),
label: const Text('Detail'), label: const Text('Detail'),
@ -432,7 +505,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
), ),
]; ];
} else if (status == 'Tindakan') { } else if (status == 'TINDAKAN') {
return [ return [
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
@ -448,8 +521,8 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// Implementasi untuk melihat detail pengaduan // Navigasi ke halaman detail pengaduan
_showDetailDialog(context, item); Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
}, },
icon: const Icon(Icons.info_outline, size: 18), icon: const Icon(Icons.info_outline, size: 18),
label: const Text('Detail'), label: const Text('Detail'),
@ -463,8 +536,8 @@ class PengaduanView extends GetView<PetugasDesaController> {
return [ return [
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// Implementasi untuk melihat detail pengaduan // Navigasi ke halaman detail pengaduan
_showDetailDialog(context, item); Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
}, },
icon: const Icon(Icons.info_outline, size: 18), icon: const Icon(Icons.info_outline, size: 18),
label: const Text('Detail'), label: const Text('Detail'),
@ -477,104 +550,42 @@ class PengaduanView extends GetView<PetugasDesaController> {
} }
} }
void _showDetailDialog(BuildContext context, Map<String, dynamic> item) { void _showTindakanDialog(BuildContext context, dynamic item) {
showDialog( controller.tindakanController.clear();
context: context, controller.catatanController.clear();
builder: (context) => AlertDialog(
title: Text('Detail Pengaduan: ${item['id']}'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildDetailItem('Nama', item['nama'] ?? ''),
_buildDetailItem('NIK', item['nik'] ?? ''),
_buildDetailItem(
'Jenis Pengaduan', item['jenis_pengaduan'] ?? ''),
_buildDetailItem('Tanggal', item['tanggal'] ?? ''),
_buildDetailItem('Status', item['status'] ?? ''),
const SizedBox(height: 8),
const Text(
'Deskripsi:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(item['deskripsi'] ?? ''),
if (item['status'] == 'Tindakan' ||
item['status'] == 'Selesai') ...[
const SizedBox(height: 8),
const Text(
'Tindakan:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(item['tindakan'] ??
'Pengecekan ke lokasi dan verifikasi data penerima'),
],
if (item['status'] == 'Selesai') ...[
const SizedBox(height: 8),
const Text(
'Hasil:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(item['hasil'] ??
'Pengaduan telah diselesaikan dengan penyaluran ulang bantuan'),
],
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Tutup'),
),
],
),
);
}
Widget _buildDetailItem(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
'$label:',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(child: Text(value)),
],
),
);
}
void _showTindakanDialog(BuildContext context, Map<String, dynamic> item) {
final TextEditingController tindakanController = TextEditingController();
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Tindakan Pengaduan'), title: const Text('Tindakan Pengaduan'),
content: Column( content: Form(
key: controller.tindakanFormKey,
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text('Pengaduan dari: ${item['nama']}'), Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextFormField(
controller: tindakanController, controller: controller.tindakanController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Tindakan yang dilakukan', labelText: 'Tindakan yang dilakukan',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
maxLines: 3, maxLines: 3,
validator: controller.validateTindakan,
),
const SizedBox(height: 12),
TextFormField(
controller: controller.catatanController,
decoration: const InputDecoration(
labelText: 'Catatan (opsional)',
border: OutlineInputBorder(),
),
maxLines: 2,
), ),
], ],
), ),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
@ -582,15 +593,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
// Implementasi untuk menyimpan tindakan controller.tambahTindakan(item.id!);
Navigator.pop(context);
Get.snackbar(
'Berhasil',
'Status pengaduan berhasil diubah menjadi Tindakan',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.blue,
colorText: Colors.white,
);
}, },
child: const Text('Simpan'), child: const Text('Simpan'),
), ),
@ -599,9 +602,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
); );
} }
void _showSelesaikanDialog(BuildContext context, Map<String, dynamic> item) { void _showSelesaikanDialog(BuildContext context, dynamic item) {
final TextEditingController hasilController = TextEditingController();
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
@ -609,15 +610,11 @@ class PengaduanView extends GetView<PetugasDesaController> {
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text('Pengaduan dari: ${item['nama']}'), Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( const Text(
controller: hasilController, 'Apakah Anda yakin ingin menyelesaikan pengaduan ini?',
decoration: const InputDecoration( textAlign: TextAlign.center,
labelText: 'Hasil penyelesaian',
border: OutlineInputBorder(),
),
maxLines: 3,
), ),
], ],
), ),
@ -628,15 +625,8 @@ class PengaduanView extends GetView<PetugasDesaController> {
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
// Implementasi untuk menyimpan hasil
Navigator.pop(context); Navigator.pop(context);
Get.snackbar( controller.selesaikanPengaduan(item.id!);
'Berhasil',
'Status pengaduan berhasil diubah menjadi Selesai',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
}, },
child: const Text('Selesaikan'), child: const Text('Selesaikan'),
), ),

View File

@ -273,11 +273,15 @@ class WargaDashboardController extends GetxController {
// Fungsi untuk mengambil data pengaduan // Fungsi untuk mengambil data pengaduan
Future<void> fetchPengaduan() async { Future<void> fetchPengaduan() async {
try { try {
final response = await _supabaseService.client final wargaData = await _supabaseService.getWargaByUserId();
.from('pengaduan') if (wargaData == null) {
.select('*') print('Data warga tidak ditemukan');
.eq('pelapor', user!.id) return;
.order('created_at', ascending: false); }
final String wargaId = wargaData['id'];
final response = await _supabaseService
.getPengaduanWargaWithPenerimaPenyaluran(wargaId);
if (response != null) { if (response != null) {
final List<PengaduanModel> pengaduanList = []; final List<PengaduanModel> pengaduanList = [];
@ -289,7 +293,7 @@ class WargaDashboardController extends GetxController {
// Hitung jumlah berdasarkan status // Hitung jumlah berdasarkan status
totalPengaduan.value = pengaduanList.length; totalPengaduan.value = pengaduanList.length;
totalPengaduanProses.value = pengaduanList totalPengaduanProses.value = pengaduanList
.where((p) => p.status == 'PROSES' || p.status == 'DIPROSES') .where((p) => p.status == 'MENUNGGU' || p.status == 'TINDAKAN')
.length; .length;
totalPengaduanSelesai.value = totalPengaduanSelesai.value =
pengaduanList.where((p) => p.status == 'SELESAI').length; pengaduanList.where((p) => p.status == 'SELESAI').length;
@ -342,4 +346,39 @@ class WargaDashboardController extends GetxController {
void logout() { void logout() {
_authController.logout(); _authController.logout();
} }
Future<Map<String, dynamic>> getDetailPengaduan(String pengaduanId) async {
try {
// Ambil data pengaduan
final pengaduanData =
await _supabaseService.client.from('pengaduan').select('''
*,
penerima_penyaluran:penerima_penyaluran_id(
*,
penyaluran_bantuan:penyaluran_bantuan_id(*),
stok_bantuan:stok_bantuan_id(*),
warga:warga_id(*)
),
warga:warga_id(*)
''').eq('id', pengaduanId).single();
// Ambil data tindakan pengaduan
final tindakanData =
await _supabaseService.getTindakanPengaduan(pengaduanId);
// Gabungkan data
final result = {
'pengaduan': pengaduanData,
'tindakan': tindakanData ?? [],
};
return result;
} catch (e) {
print('Error getting detail pengaduan: $e');
return {
'pengaduan': null,
'tindakan': [],
};
}
}
} }

View File

@ -0,0 +1,449 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/data/models/pengaduan_model.dart';
import 'package:penyaluran_app/app/data/models/tindakan_pengaduan_model.dart';
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart';
import 'package:timeline_tile/timeline_tile.dart';
class WargaDetailPengaduanView extends GetView<WargaDashboardController> {
const WargaDetailPengaduanView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final Map<String, dynamic> args = Get.arguments ?? {};
final String pengaduanId = args['id'] ?? '';
if (pengaduanId.isEmpty) {
return Scaffold(
appBar: AppBar(
title: const Text('Detail Pengaduan'),
),
body: const Center(
child: Text('ID Pengaduan tidak valid'),
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Detail Pengaduan'),
elevation: 0,
),
body: FutureBuilder<Map<String, dynamic>>(
future: controller.getDetailPengaduan(pengaduanId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
}
final data = snapshot.data;
if (data == null || data['pengaduan'] == null) {
return const Center(
child: Text('Data pengaduan tidak ditemukan'),
);
}
final pengaduan = PengaduanModel.fromJson(data['pengaduan']);
final List<TindakanPengaduanModel> tindakanList =
(data['tindakan'] as List)
.map((item) => TindakanPengaduanModel.fromJson(item))
.toList();
return _buildDetailContent(context, pengaduan, tindakanList);
},
),
);
}
Widget _buildDetailContent(
BuildContext context,
PengaduanModel pengaduan,
List<TindakanPengaduanModel> tindakanList,
) {
// Tentukan status dan warna
Color statusColor;
String statusText;
switch (pengaduan.status?.toUpperCase()) {
case 'MENUNGGU':
statusColor = Colors.orange;
statusText = 'Menunggu';
break;
case 'TINDAKAN':
statusColor = Colors.blue;
statusText = 'Tindakan';
break;
case 'SELESAI':
statusColor = Colors.green;
statusText = 'Selesai';
break;
default:
statusColor = Colors.grey;
statusText = pengaduan.status ?? 'Tidak Diketahui';
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header dengan status
_buildHeaderWithStatus(context, pengaduan, statusColor, statusText),
const SizedBox(height: 24),
// Informasi penyaluran yang diadukan
if (pengaduan.penerimaPenyaluran != null)
_buildPenyaluranInfo(context, pengaduan),
const SizedBox(height: 24),
// Timeline tindakan
_buildTindakanTimeline(context, tindakanList),
],
),
);
}
Widget _buildHeaderWithStatus(
BuildContext context,
PengaduanModel pengaduan,
Color statusColor,
String statusText,
) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
pengaduan.judul ?? 'Pengaduan',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: statusColor,
),
),
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 12),
Text(
pengaduan.deskripsi ?? '',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
),
const SizedBox(height: 12),
Row(
children: [
Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey.shade600,
),
const SizedBox(width: 8),
Text(
pengaduan.tanggalPengaduan != null
? DateFormat('dd MMMM yyyy', 'id_ID')
.format(pengaduan.tanggalPengaduan!)
: '-',
style: TextStyle(
color: Colors.grey.shade600,
),
),
],
),
],
),
),
);
}
Widget _buildPenyaluranInfo(BuildContext context, PengaduanModel pengaduan) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Informasi Penyaluran',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
_buildInfoRow('Nama Penyaluran', pengaduan.namaPenyaluran),
_buildInfoRow('Jenis Bantuan', pengaduan.jenisBantuan),
_buildInfoRow('Jumlah Bantuan', pengaduan.jumlahBantuan),
_buildInfoRow('Deskripsi', pengaduan.deskripsiPenyaluran),
],
),
),
);
}
Widget _buildTindakanTimeline(
BuildContext context,
List<TindakanPengaduanModel> tindakanList,
) {
if (tindakanList.isEmpty) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: const Padding(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
'Pengaduan Anda sedang menunggu tindakan dari petugas',
style: TextStyle(
fontSize: 14,
fontStyle: FontStyle.italic,
color: Colors.grey,
),
),
),
),
);
}
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Riwayat Tindakan',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: tindakanList.length,
itemBuilder: (context, index) {
final tindakan = tindakanList[index];
final bool isFirst = index == 0;
final bool isLast = index == tindakanList.length - 1;
return _buildTimelineTile(
context,
tindakan,
isFirst,
isLast,
);
},
),
],
),
),
);
}
Widget _buildTimelineTile(
BuildContext context,
TindakanPengaduanModel tindakan,
bool isFirst,
bool isLast,
) {
Color dotColor;
switch (tindakan.statusTindakan) {
case 'SELESAI':
dotColor = Colors.green;
break;
case 'PROSES':
dotColor = Colors.blue;
break;
default:
dotColor = Colors.grey;
}
return TimelineTile(
alignment: TimelineAlign.start,
isFirst: isFirst,
isLast: isLast,
indicatorStyle: IndicatorStyle(
width: 20,
color: dotColor,
iconStyle: IconStyle(
color: Colors.white,
iconData:
tindakan.statusTindakan == 'SELESAI' ? Icons.check : Icons.sync,
),
),
endChild: Container(
margin: const EdgeInsets.only(left: 16, bottom: 24),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
tindakan.kategoriTindakanText,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: dotColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
tindakan.statusTindakanText,
style: TextStyle(
color: dotColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 8),
Text(
tindakan.tindakan ?? '',
style: const TextStyle(fontSize: 14),
),
if (tindakan.hasilTindakan != null &&
tindakan.hasilTindakan!.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
'Hasil: ${tindakan.hasilTindakan}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
),
),
],
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Oleh: ${tindakan.namaPetugas}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
Text(
tindakan.tanggalTindakan != null
? DateFormat('dd MMM yyyy', 'id_ID')
.format(tindakan.tanggalTindakan!)
: '-',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
'$label:',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
color: Colors.black87,
),
),
),
],
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.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/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';
@ -290,10 +291,9 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
title: 'Bantuan Terbaru', title: 'Bantuan Terbaru',
viewAllText: 'Lihat Semua', viewAllText: 'Lihat Semua',
onViewAll: () { onViewAll: () {
Get.toNamed('/warga-penerimaan'); Get.toNamed(Routes.wargaPenerimaan);
}, },
), ),
const SizedBox(height: 16),
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.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/utils/date_time_helper.dart';
class WargaPengaduanView extends GetView<WargaDashboardController> { class WargaPengaduanView extends GetView<WargaDashboardController> {
const WargaPengaduanView({Key? key}) : super(key: key); const WargaPengaduanView({Key? key}) : super(key: key);
@ -83,11 +84,13 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
String statusText; String statusText;
switch (item.status?.toUpperCase()) { switch (item.status?.toUpperCase()) {
case 'PROSES': case 'MENUNGGU':
case 'DIPROSES':
case 'TINDAKAN':
statusColor = Colors.orange; statusColor = Colors.orange;
statusText = 'Proses'; statusText = 'Menunggu';
break;
case 'TINDAKAN':
statusColor = Colors.blue;
statusText = 'Tindakan';
break; break;
case 'SELESAI': case 'SELESAI':
statusColor = Colors.green; statusColor = Colors.green;
@ -102,8 +105,6 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
statusText = item.status ?? 'Tidak Diketahui'; statusText = item.status ?? 'Tidak Diketahui';
} }
final isProses = statusText == 'Proses';
return Card( return Card(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -112,7 +113,9 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
elevation: 2, elevation: 2,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
// TODO: Navigasi ke detail pengaduan // Navigasi ke detail pengaduan
Get.toNamed('/warga/detail-pengaduan',
arguments: {'id': item.id});
}, },
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Padding( child: Padding(
@ -158,6 +161,52 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Informasi penyaluran bantuan
if (item.penerimaPenyaluran != null)
Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Penyaluran: ${item.namaPenyaluran}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Row(
children: [
Expanded(
child: Text(
'Jenis: ${item.jenisBantuan}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
),
),
),
Expanded(
child: Text(
'Jumlah: ${item.jumlahBantuan}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
),
),
),
],
),
],
),
),
if (item.deskripsi != null && item.deskripsi!.isNotEmpty) if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
Padding( Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
@ -180,8 +229,8 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
item.tanggalPengaduan != null item.tanggalPengaduan != null
? DateFormat('dd MMMM yyyy', 'id_ID') ? DateTimeHelper.formatDateTime(
.format(item.tanggalPengaduan!) item.tanggalPengaduan!)
: '-', : '-',
style: TextStyle( style: TextStyle(
color: Colors.grey.shade600, color: Colors.grey.shade600,
@ -197,8 +246,8 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
children: [ children: [
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
// TODO: Implementasi navigasi ke detail pengaduan // Navigasi ke detail pengaduan
Get.toNamed('/detail-pengaduan', Get.toNamed('/warga/detail-pengaduan',
arguments: {'id': item.id}); arguments: {'id': item.id});
}, },
icon: const Icon(Icons.visibility), icon: const Icon(Icons.visibility),

View File

@ -27,6 +27,9 @@ import 'package:penyaluran_app/app/modules/warga/views/warga_pengaduan_view.dart
import 'package:penyaluran_app/app/modules/warga/views/warga_view.dart'; import 'package:penyaluran_app/app/modules/warga/views/warga_view.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/warga_detail_penerimaan_view.dart'; import 'package:penyaluran_app/app/modules/warga/views/warga_detail_penerimaan_view.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_pengaduan_view.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/pengaduan_binding.dart';
import 'package:penyaluran_app/app/modules/warga/views/detail_pengaduan_view.dart';
part 'app_routes.dart'; part 'app_routes.dart';
@ -124,10 +127,20 @@ class AppPages {
page: () => DetailPenyaluranPage(), page: () => DetailPenyaluranPage(),
binding: PenyaluranBinding(), binding: PenyaluranBinding(),
), ),
GetPage(
name: _Paths.detailPengaduan,
page: () => const DetailPengaduanView(),
binding: PengaduanBinding(),
),
GetPage( GetPage(
name: Routes.wargaDetailPenerimaan, name: Routes.wargaDetailPenerimaan,
page: () => const WargaDetailPenerimaanView(), page: () => const WargaDetailPenerimaanView(),
binding: WargaBinding(), binding: WargaBinding(),
), ),
GetPage(
name: Routes.wargaDetailPengaduan,
page: () => const WargaDetailPengaduanView(),
binding: WargaBinding(),
),
]; ];
} }

View File

@ -28,6 +28,8 @@ abstract class Routes {
static const detailPenyaluran = _Paths.detailPenyaluran; static const detailPenyaluran = _Paths.detailPenyaluran;
static const riwayatPenyaluran = _Paths.riwayatPenyaluran; static const riwayatPenyaluran = _Paths.riwayatPenyaluran;
static const wargaDetailPenerimaan = _Paths.wargaDetailPenerimaan; static const wargaDetailPenerimaan = _Paths.wargaDetailPenerimaan;
static const detailPengaduan = _Paths.detailPengaduan;
static const wargaDetailPengaduan = _Paths.wargaDetailPengaduan;
} }
abstract class _Paths { abstract class _Paths {
@ -58,4 +60,6 @@ abstract class _Paths {
static const detailPenyaluran = '/detail-penyaluran'; static const detailPenyaluran = '/detail-penyaluran';
static const riwayatPenyaluran = '/petugas-desa/riwayat-penyaluran'; static const riwayatPenyaluran = '/petugas-desa/riwayat-penyaluran';
static const wargaDetailPenerimaan = '/warga/detail-penerimaan'; static const wargaDetailPenerimaan = '/warga/detail-penerimaan';
static const detailPengaduan = '/detail-pengaduan';
static const wargaDetailPengaduan = '/warga/detail-pengaduan';
} }

View File

@ -701,6 +701,50 @@ class SupabaseService extends GetxService {
} }
} }
// Metode untuk mendapatkan pengaduan dengan detail penerima penyaluran
Future<List<Map<String, dynamic>>?>
getPengaduanWithPenerimaPenyaluran() async {
try {
final response = await client.from('pengaduan').select('''
*,
penerima_penyaluran:penerima_penyaluran_id(
*,
penyaluran_bantuan:penyaluran_bantuan_id(*),
stok_bantuan:stok_bantuan_id(*),
warga:warga_id(*)
),
warga:warga_id(*)
''').order('created_at', ascending: false);
return response;
} catch (e) {
print('Error getting pengaduan with penerima penyaluran: $e');
return null;
}
}
// Metode untuk mendapatkan pengaduan warga tertentu dengan detail penerima penyaluran
Future<List<Map<String, dynamic>>?> getPengaduanWargaWithPenerimaPenyaluran(
String wargaId) async {
try {
final response = await client.from('pengaduan').select('''
*,
penerima_penyaluran:penerima_penyaluran_id(
*,
penyaluran_bantuan:penyaluran_bantuan_id(*),
stok_bantuan:stok_bantuan_id(*),
warga:warga_id(*)
),
warga:warga_id(*)
''').eq('warga_id', wargaId).order('created_at', ascending: false);
return response;
} catch (e) {
print('Error getting warga pengaduan with penerima penyaluran: $e');
return null;
}
}
Future<void> prosesPengaduan(String pengaduanId) async { Future<void> prosesPengaduan(String pengaduanId) async {
try { try {
await client.from('pengaduan').update({ await client.from('pengaduan').update({
@ -722,6 +766,19 @@ class SupabaseService extends GetxService {
} }
} }
Future<void> updateTindakanPengaduan(
String tindakanId, Map<String, dynamic> tindakan) async {
try {
await client
.from('tindakan_pengaduan')
.update(tindakan)
.eq('id', tindakanId);
} catch (e) {
print('Error updating tindakan pengaduan: $e');
throw e.toString();
}
}
Future<void> updateStatusPengaduan(String pengaduanId, String status) async { Future<void> updateStatusPengaduan(String pengaduanId, String status) async {
try { try {
await client.from('pengaduan').update({ await client.from('pengaduan').update({
@ -739,7 +796,11 @@ class SupabaseService extends GetxService {
try { try {
final response = await client final response = await client
.from('tindakan_pengaduan') .from('tindakan_pengaduan')
.select('*') .select('''
*,
petugas:petugas_id(id, nama, email),
verifikator:verifikator_id(id, nama, email)
''')
.eq('pengaduan_id', pengaduanId) .eq('pengaduan_id', pengaduanId)
.order('created_at', ascending: false); .order('created_at', ascending: false);

View File

@ -14,7 +14,7 @@ class SectionHeader extends StatelessWidget {
this.onViewAll, this.onViewAll,
this.viewAllText = 'Lihat Semua', this.viewAllText = 'Lihat Semua',
this.trailing, this.trailing,
this.padding = const EdgeInsets.only(bottom: 12), this.padding = const EdgeInsets.only(bottom: 4),
this.titleStyle, this.titleStyle,
}) : super(key: key); }) : super(key: key);

View File

@ -834,6 +834,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.4" version: "0.7.4"
timeline_tile:
dependency: "direct main"
description:
name: timeline_tile
sha256: "85ec2023c67137397c2812e3e848b2fb20b410b67cd9aff304bb5480c376fc0c"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:

View File

@ -70,6 +70,7 @@ dependencies:
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_staggered_animations: ^1.1.1 flutter_staggered_animations: ^1.1.1
timeline_tile: ^2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: