Perbarui model dan tampilan untuk mendukung perubahan struktur data pengguna. Ganti properti nama dan telepon dengan namaLengkap dan noHp di beberapa model, termasuk DonaturModel, PetugasDesaModel, dan WargaModel. Modifikasi tampilan dan controller untuk menggunakan properti baru ini. Tambahkan fungsionalitas baru untuk menampilkan nama lengkap dan nomor telepon dengan lebih baik di berbagai tampilan. Perbarui rute dan logika aplikasi untuk mencerminkan perubahan ini.

This commit is contained in:
Khafidh Fuadi
2025-03-25 10:43:21 +07:00
parent 54c4660302
commit 8e9553d1fc
39 changed files with 1819 additions and 704 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  ٬ك<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>
 
}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  ٬ك<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  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2~
| |
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  ٬ك<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  <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\arm64-v8a\android_gradle_build_mini.json  ٬ك<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  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p
n n
lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  ٬ك<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  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t
r r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  ٬ك<EFBFBD>2y pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2y
w w
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  ٬ك<EFBFBD>2 uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2z K <20><><EFBFBD><EFBFBD><EFBFBD>2z
x x
vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 ~ vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 ~
| |
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2 zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
<EFBFBD> <EFBFBD>
<EFBFBD> <EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2w <EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2w
u u
sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2|  ( <20><><EFBFBD><EFBFBD><EFBFBD>2|

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>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>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>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  íك<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~

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>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

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>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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,24 +1,26 @@
import 'dart:convert'; import 'dart:convert';
class DonaturModel { class DonaturModel {
final String? id; final String id; // Primary key yang juga foreign key ke auth.users(id)
final String? nama; final String? namaLengkap;
final String? alamat; final String? alamat;
final String? telepon; final String? noHp;
final String? email; final String? email;
final String? jenis; final String? jenis;
final String? deskripsi;
final String? status; final String? status;
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
DonaturModel({ DonaturModel({
this.id, required this.id,
this.nama, this.namaLengkap,
this.alamat, this.alamat,
this.telepon, this.noHp,
this.email, this.email,
this.jenis, this.jenis,
this.status, this.deskripsi,
this.status = 'AKTIF',
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
}); });
@ -30,11 +32,12 @@ class DonaturModel {
factory DonaturModel.fromJson(Map<String, dynamic> json) => DonaturModel( factory DonaturModel.fromJson(Map<String, dynamic> json) => DonaturModel(
id: json["id"], id: json["id"],
nama: json["nama"], namaLengkap: json["nama_lengkap"],
alamat: json["alamat"], alamat: json["alamat"],
telepon: json["telepon"], noHp: json["no_hp"],
email: json["email"], email: json["email"],
jenis: json["jenis"], jenis: json["jenis"],
deskripsi: json["deskripsi"],
status: json["status"] ?? 'AKTIF', status: json["status"] ?? 'AKTIF',
createdAt: json["created_at"] != null createdAt: json["created_at"] != null
? DateTime.parse(json["created_at"]) ? DateTime.parse(json["created_at"])
@ -46,13 +49,20 @@ class DonaturModel {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"id": id, "id": id,
"nama": nama, "nama_lengkap": namaLengkap,
"alamat": alamat, "alamat": alamat,
"telepon": telepon, "no_hp": noHp,
"email": email, "email": email,
"jenis": jenis, "jenis": jenis,
"deskripsi": deskripsi,
"status": status ?? 'AKTIF', "status": status ?? 'AKTIF',
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
}; };
// Helper method untuk mendapatkan nama yang ditampilkan
String get displayName => namaLengkap ?? 'Donatur';
// Getter untuk kompatibilitas dengan kode yang masih menggunakan nama
String? get nama => namaLengkap;
} }

View File

@ -1,26 +1,31 @@
import 'dart:convert'; import 'dart:convert';
import 'package:penyaluran_app/app/data/models/desa_model.dart';
class PetugasDesaModel { class PetugasDesaModel {
final String? id; final String id; // Primary key yang juga foreign key ke auth.users(id)
final String? nama; final String? desaId;
final String? alamatLengkap; final String? namaLengkap;
final String? noTelp; final String? alamat;
final String? noHp;
final String? email; final String? email;
final String? jabatan; final String? jabatan;
final String? userId; final String? nip;
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
final DesaModel? desa;
PetugasDesaModel({ PetugasDesaModel({
this.id, required this.id,
this.nama, this.desaId,
this.alamatLengkap, this.namaLengkap,
this.noTelp, this.alamat,
this.noHp,
this.email, this.email,
this.jabatan, this.jabatan,
this.userId, this.nip,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
this.desa,
}); });
factory PetugasDesaModel.fromRawJson(String str) => factory PetugasDesaModel.fromRawJson(String str) =>
@ -28,32 +33,44 @@ class PetugasDesaModel {
String toRawJson() => json.encode(toJson()); String toRawJson() => json.encode(toJson());
factory PetugasDesaModel.fromJson(Map<String, dynamic> json) => factory PetugasDesaModel.fromJson(Map<String, dynamic> json) {
PetugasDesaModel( DesaModel? desa;
if (json["desa"] != null && json["desa"] is Map<String, dynamic>) {
desa = DesaModel.fromJson(json["desa"]);
}
return PetugasDesaModel(
id: json["id"], id: json["id"],
nama: json["nama"], desaId: json["desa_id"],
alamatLengkap: json["alamat_lengkap"], namaLengkap: json["nama_lengkap"],
noTelp: json["no_telp"], alamat: json["alamat"],
noHp: json["no_hp"],
email: json["email"], email: json["email"],
jabatan: json["jabatan"], jabatan: json["jabatan"],
userId: json["user_id"], nip: json["nip"],
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,
desa: desa,
); );
}
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"id": id, "id": id,
"nama": nama, "desa_id": desaId,
"alamat_lengkap": alamatLengkap, "nama_lengkap": namaLengkap,
"no_telp": noTelp, "alamat": alamat,
"no_hp": noHp,
"email": email, "email": email,
"jabatan": jabatan, "jabatan": jabatan,
"user_id": userId, "nip": nip,
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
}; };
// Helper method untuk mendapatkan nama yang ditampilkan
String get displayName => namaLengkap ?? 'Petugas Desa';
} }

View File

@ -101,20 +101,16 @@ class TindakanPengaduanModel {
// Getter untuk mendapatkan nama petugas // Getter untuk mendapatkan nama petugas
String get namaPetugas { String get namaPetugas {
if (petugas != null && petugas!['nama'] != null) { if (petugas != null && petugas!['nama_lengkap'] != null) {
return petugas!['nama']; return petugas!['nama_lengkap'];
} else if (petugas != null && petugas!['name'] != null) {
return petugas!['name'];
} }
return 'Petugas'; return 'Petugas';
} }
// Getter untuk mendapatkan nama verifikator // Getter untuk mendapatkan nama verifikator
String get namaVerifikator { String get namaVerifikator {
if (verifikator != null && verifikator!['nama'] != null) { if (verifikator != null && verifikator!['nama_lengkap'] != null) {
return verifikator!['nama']; return verifikator!['nama_lengkap'];
} else if (verifikator != null && verifikator!['name'] != null) {
return verifikator!['name'];
} }
return 'Belum diverifikasi'; return 'Belum diverifikasi';
} }

View File

@ -1,51 +1,58 @@
import 'package:penyaluran_app/app/data/models/desa_model.dart'; import 'package:penyaluran_app/app/data/models/desa_model.dart';
class UserModel { // Model dasar untuk pengguna dengan informasi autentikasi umum
class BaseUserModel {
final String id; final String id;
final String email; final String email;
final String? name; final int? roleId;
final String? avatar; final String roleName;
final String role;
final bool isActive; final bool isActive;
final DesaModel? desa;
final String? desaId;
final DateTime? lastLogin; final DateTime? lastLogin;
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
final String? name;
final String? role;
final DesaModel? desa;
UserModel({ BaseUserModel({
required this.id, required this.id,
required this.email, required this.email,
this.name, this.roleId,
this.avatar, required this.roleName,
required this.role,
this.isActive = true, this.isActive = true,
this.desa,
this.desaId,
this.lastLogin, this.lastLogin,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
this.name,
this.role,
this.desa,
}); });
factory UserModel.fromJson(Map<String, dynamic> json) { factory BaseUserModel.fromJson(Map<String, dynamic> json) {
// Pastikan id dan email tidak null
if (json['id'] == null || json['email'] == null) { if (json['id'] == null || json['email'] == null) {
throw Exception('UserModel: id dan email tidak boleh null'); throw Exception('BaseUserModel: id dan email tidak boleh null');
} }
// Parse desa jika ada // Dapatkan roleName, default ke 'warga' jika tidak tersedia
DesaModel? desaModel; String roleName = 'warga';
if (json['roles'] != null && json['roles']['role_name'] != null) {
roleName = json['roles']['role_name'];
} else if (json['role'] != null) {
roleName = json['role'];
}
// Parse desa data jika ada
DesaModel? desa;
if (json['desa'] != null && json['desa'] is Map<String, dynamic>) { if (json['desa'] != null && json['desa'] is Map<String, dynamic>) {
desaModel = DesaModel.fromJson(json['desa'] as Map<String, dynamic>); desa = DesaModel.fromJson(json['desa']);
} }
return UserModel( return BaseUserModel(
id: json['id'], id: json['id'],
email: json['email'], email: json['email'],
name: json['name'], roleId: json['role_id'],
avatar: json['avatar'], roleName: roleName,
desa: desaModel,
desaId: json['desa_id'],
role: json['role'] ?? 'WARGA',
isActive: json['is_active'] ?? true, isActive: json['is_active'] ?? true,
lastLogin: json['last_login'] != null lastLogin: json['last_login'] != null
? DateTime.parse(json['last_login']) ? DateTime.parse(json['last_login'])
@ -56,6 +63,9 @@ class UserModel {
updatedAt: json['updated_at'] != null updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at']) ? DateTime.parse(json['updated_at'])
: null, : null,
name: json['name'],
role: roleName,
desa: desa,
); );
} }
@ -63,83 +73,25 @@ class UserModel {
return { return {
'id': id, 'id': id,
'email': email, 'email': email,
'name': name, 'role_id': roleId,
'avatar': avatar, 'role': roleName,
'desa_id': desaId,
'desa': desa?.toJson(),
'role': role,
'is_active': isActive, 'is_active': isActive,
'last_login': lastLogin?.toIso8601String(), 'last_login': lastLogin?.toIso8601String(),
'created_at': createdAt?.toIso8601String(), 'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(),
};
}
UserModel copyWith({
String? id,
String? email,
String? name,
String? avatar,
DesaModel? desa,
String? desaId,
String? role,
bool? isActive,
DateTime? lastLogin,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return UserModel(
id: id ?? this.id,
email: email ?? this.email,
name: name ?? this.name,
avatar: avatar ?? this.avatar,
desa: desa ?? this.desa,
desaId: desaId ?? this.desaId,
role: role ?? this.role,
isActive: isActive ?? this.isActive,
lastLogin: lastLogin ?? this.lastLogin,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
class User {
final String? id;
final String? name;
final String? email;
final String? phone;
final String? role;
final String? token;
User({
this.id,
this.name,
this.email,
this.phone,
this.role,
this.token,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
phone: json['phone'],
role: json['role'],
token: json['token'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name, 'name': name,
'email': email, 'desa': desa?.toJson(),
'phone': phone,
'role': role,
'token': token,
}; };
} }
} }
// Class untuk menampung data user lengkap (BaseUserModel + data spesifik role)
class UserData<T> {
final BaseUserModel baseUser;
final T roleData;
UserData({
required this.baseUser,
required this.roleData,
});
}

View File

@ -1,41 +1,44 @@
import 'dart:convert'; import 'dart:convert';
import 'package:penyaluran_app/app/data/models/desa_model.dart';
// warga == penerima bantuan // warga == penerima bantuan
class WargaModel { class WargaModel {
final String? id; final String id; // Primary key yang juga foreign key ke auth.users(id)
final String? nama; final String? desaId;
final String? nik; final String? namaLengkap;
final String? alamat; final String? alamat;
final String? desa; final String? noHp;
final String? kecamatan;
final String? kabupaten;
final String? provinsi;
final String? telepon;
final String? email; final String? email;
final String? nik;
final String? tempatLahir;
final DateTime? tanggalLahir;
final String? jenisKelamin;
final String? agama;
final String? kategoriEkonomi;
final String? status;
final String? catatan; final String? catatan;
final String? kategori; // Contoh: 'lansia', 'disabilitas', 'miskin', dll
final String? status; // Contoh: 'AKTIF', 'NONAKTIF'
final String? lokasiPenyaluranId; // Referensi ke LokasiPenyaluran
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
final DesaModel? desa;
WargaModel({ WargaModel({
this.id, required this.id,
this.nama, this.desaId,
this.nik, this.namaLengkap,
this.alamat, this.alamat,
this.desa, this.noHp,
this.kecamatan,
this.kabupaten,
this.provinsi,
this.telepon,
this.email, this.email,
this.nik,
this.tempatLahir,
this.tanggalLahir,
this.jenisKelamin,
this.agama,
this.kategoriEkonomi,
this.status = 'AKTIF',
this.catatan, this.catatan,
this.kategori,
this.status,
this.lokasiPenyaluranId,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
this.desa,
}); });
factory WargaModel.fromRawJson(String str) => factory WargaModel.fromRawJson(String str) =>
@ -43,45 +46,58 @@ class WargaModel {
String toRawJson() => json.encode(toJson()); String toRawJson() => json.encode(toJson());
factory WargaModel.fromJson(Map<String, dynamic> json) => WargaModel( factory WargaModel.fromJson(Map<String, dynamic> json) {
DesaModel? desa;
if (json["desa"] != null && json["desa"] is Map<String, dynamic>) {
desa = DesaModel.fromJson(json["desa"]);
}
return WargaModel(
id: json["id"], id: json["id"],
nama: json["nama"], desaId: json["desa_id"],
nik: json["nik"], namaLengkap: json["nama_lengkap"],
alamat: json["alamat"], alamat: json["alamat"],
desa: json["desa"], noHp: json["no_hp"],
kecamatan: json["kecamatan"],
kabupaten: json["kabupaten"],
provinsi: json["provinsi"],
telepon: json["telepon"] ?? json["no_telp"],
email: json["email"], email: json["email"],
nik: json["nik"],
tempatLahir: json["tempat_lahir"],
tanggalLahir: json["tanggal_lahir"] != null
? DateTime.parse(json["tanggal_lahir"])
: null,
jenisKelamin: json["jenis_kelamin"],
agama: json["agama"],
kategoriEkonomi: json["kategori_ekonomi"],
status: json["status"] ?? 'AKTIF',
catatan: json["catatan"], catatan: json["catatan"],
kategori: json["kategori"],
status: json["status"],
lokasiPenyaluranId: json["lokasi_penyaluran_id"],
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,
desa: desa,
); );
}
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"id": id, "id": id,
"nama": nama, "desa_id": desaId,
"nik": nik, "nama_lengkap": namaLengkap,
"alamat": alamat, "alamat": alamat,
"desa": desa, "no_hp": noHp,
"kecamatan": kecamatan,
"kabupaten": kabupaten,
"provinsi": provinsi,
"telepon": telepon,
"email": email, "email": email,
"catatan": catatan, "nik": nik,
"kategori": kategori, "tempat_lahir": tempatLahir,
"tanggal_lahir": tanggalLahir?.toIso8601String(),
"jenis_kelamin": jenisKelamin,
"agama": agama,
"kategori_ekonomi": kategoriEkonomi,
"status": status, "status": status,
"lokasi_penyaluran_id": lokasiPenyaluranId, "catatan": catatan,
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
}; };
// Helper method untuk mendapatkan nama yang ditampilkan
String get displayName => namaLengkap ?? 'Warga';
} }

View File

@ -1,44 +1,162 @@
import 'package:penyaluran_app/app/services/supabase_service.dart'; import 'package:penyaluran_app/app/services/supabase_service.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart'; import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/warga_model.dart';
import 'package:penyaluran_app/app/data/models/petugas_desa_model.dart';
import 'package:penyaluran_app/app/data/models/donatur_model.dart';
import 'package:penyaluran_app/app/data/models/desa_model.dart';
class AuthProvider { class AuthProvider {
final SupabaseService _supabaseService = SupabaseService.to; final SupabaseService _supabaseService = SupabaseService.to;
// Cache untuk menyimpan data profil pengguna // Cache untuk menyimpan data pengguna
UserModel? _cachedUser; UserData? _cachedUserData;
// Metode untuk login // Metode untuk login
Future<UserModel?> signIn(String email, String password) async { Future<UserData?> signIn(String email, String password) async {
try { try {
final response = await _supabaseService.signIn(email, password); // Step 1: Login
final response = await _supabaseService.client.auth.signInWithPassword(
if (response.user != null && response.user?.email != null) { email: email,
// Ambil profil pengguna dari database password: password,
final profileData = await _supabaseService.getUserProfile();
print('DEBUG: Profile data dari signIn: $profileData');
if (profileData != null) {
// Buat UserModel dengan data yang ada
_cachedUser = UserModel.fromJson({
...profileData,
'id': response.user!.id,
'email': response.user!.email!,
});
print(
'DEBUG: User model dibuat: ${_cachedUser?.name}, desa: ${_cachedUser?.desa?.nama}');
return _cachedUser;
}
// Jika profil belum tersedia, gunakan data default
_cachedUser = UserModel(
id: response.user!.id,
email: response.user!.email!,
role: 'WARGA', // Default role
); );
print('DEBUG: User model default dibuat: ${_cachedUser?.email}');
return _cachedUser; final user = response.user;
} if (user == null) {
print('Login gagal!');
return null; return null;
}
final userId = user.id;
// Step 2: Ambil role dari view `users_with_roles` atau tabel roles
String roleName;
try {
final roleResponse = await _supabaseService.client
.from('users_with_roles')
.select('role_name')
.eq('id', userId)
.single();
roleName = roleResponse['role_name'];
} catch (e) {
print('Error mengambil role dari users_with_roles: $e');
print('Mencoba ambil role dari tabel roles...');
// Fallback ke cara lama jika view users_with_roles tidak tersedia
try {
// Ambil role_id dari user metadata
final roleId = user.userMetadata?['role_id'];
if (roleId == null) {
print('Tidak ada role_id di user metadata');
return null;
}
final roleResponse = await _supabaseService.client
.from('roles')
.select('role_name')
.eq('id', roleId)
.single();
roleName = roleResponse['role_name'];
} catch (e) {
print('Error mengambil role dari tabel roles: $e');
return null;
}
}
print('Role: $roleName');
// Step 3: Ambil profil user berdasarkan role
Map<String, dynamic>? profileResponse;
dynamic roleData;
DesaModel? desa;
// Buat BaseUserModel
final baseUser = BaseUserModel(
id: userId,
email: user.email ?? '',
roleId: user.userMetadata?['role_id'],
roleName: roleName,
createdAt: DateTime.parse(user.createdAt),
updatedAt:
user.updatedAt != null ? DateTime.parse(user.updatedAt!) : null,
);
if (roleName == 'warga') {
profileResponse = await _supabaseService.client
.from('warga')
.select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId)
.single();
if (profileResponse != null) {
// Ekstrak data desa jika ada
if (profileResponse['desa'] != null) {
desa = DesaModel.fromJson(profileResponse['desa']);
print('Data Desa: ${desa.nama}');
}
roleData = WargaModel.fromJson(profileResponse);
print('Data Warga: ${roleData.namaLengkap}');
} else {
print('Tidak menemukan data warga untuk ID: $userId');
return null;
}
} else if (roleName == 'petugas_desa') {
profileResponse = await _supabaseService.client
.from('petugas_desa')
.select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId)
.single();
if (profileResponse != null) {
// Ekstrak data desa jika ada
if (profileResponse['desa'] != null) {
desa = DesaModel.fromJson(profileResponse['desa']);
print('Data Desa: ${desa.nama}');
}
roleData = PetugasDesaModel.fromJson(profileResponse);
print(
'Data Petugas Desa: ${roleData.namaLengkap}, Desa: ${roleData.desa?.nama}');
}
} else if (roleName == 'donatur') {
profileResponse = await _supabaseService.client
.from('donatur')
.select('*')
.eq('id', userId)
.single();
roleData = DonaturModel.fromJson(profileResponse);
print('Data Donatur: ${roleData.namaLengkap}');
} else {
return null;
}
if (roleData == null) {
print('Tidak menemukan data profil untuk role: $roleName');
return null;
}
// Perbarui baseUser dengan data desa jika ada
final updatedBaseUser = BaseUserModel(
id: baseUser.id,
email: baseUser.email,
roleId: baseUser.roleId,
roleName: baseUser.roleName,
createdAt: baseUser.createdAt,
updatedAt: baseUser.updatedAt,
desa: desa, // Set desa dari data yang diambil
);
// Simpan cache user data
final userData = UserData(
baseUser: updatedBaseUser,
roleData: roleData,
);
_cachedUserData = userData;
return userData;
} catch (e) { } catch (e) {
print('Error pada signIn: $e'); print('Error pada signIn: $e');
rethrow; rethrow;
@ -49,57 +167,159 @@ class AuthProvider {
Future<void> signOut() async { Future<void> signOut() async {
try { try {
await _supabaseService.signOut(); await _supabaseService.signOut();
_cachedUser = null; // Hapus cache saat logout _cachedUserData = null; // Hapus cache saat logout
} catch (e) { } catch (e) {
rethrow; rethrow;
} }
} }
// Metode untuk mendapatkan user saat ini // Metode untuk mendapatkan user saat ini
Future<UserModel?> getCurrentUser() async { Future<UserData?> getCurrentUser({bool skipCache = false}) async {
// Jika ada cache dan user masih terautentikasi, gunakan cache // Jika ada cache dan user masih terautentikasi, gunakan cache kecuali skipCache = true
if (_cachedUser != null && _supabaseService.isAuthenticated) { if (!skipCache &&
print( _cachedUserData != null &&
'DEBUG: Menggunakan data user dari cache: ${_cachedUser?.name}, desa: ${_cachedUser?.desa?.nama}'); _supabaseService.isAuthenticated) {
return _cachedUser; print('DEBUG: Menggunakan data user dari cache');
return _cachedUserData;
} }
final user = _supabaseService.currentUser; if (_supabaseService.currentUser != null) {
if (user != null) {
try { try {
// Ambil profil pengguna dari database // Login berhasil, lakukan proses yang sama seperti di signIn
final profileData = await _supabaseService.getUserProfile(); // tapi dengan user yang sudah ada
print('DEBUG: Profile data dari getCurrentUser: $profileData'); final user = _supabaseService.currentUser!;
final userId = user.id;
if (profileData != null) { // Step 2: Ambil role dari view `users_with_roles` atau tabel roles
// Buat UserModel dengan data yang ada String roleName;
_cachedUser = UserModel.fromJson({ try {
...profileData, final roleResponse = await _supabaseService.client
'id': user.id, .from('users_with_roles')
'email': user.email!, .select('role_name')
}); .eq('id', userId)
print( .single();
'DEBUG: User model dibuat: ${_cachedUser?.name}, desa: ${_cachedUser?.desa?.nama}');
return _cachedUser; roleName = roleResponse['role_name'];
} catch (e) {
print('Error mengambil role dari users_with_roles: $e');
print('Mencoba ambil role dari tabel roles...');
// Fallback ke cara lama jika view users_with_roles tidak tersedia
try {
// Ambil role_id dari user metadata
final roleId = user.userMetadata?['role_id'];
if (roleId == null) {
print('Tidak ada role_id di user metadata');
return null;
} }
// Jika profil belum tersedia, gunakan data default final roleResponse = await _supabaseService.client
_cachedUser = UserModel( .from('roles')
id: user.id, .select('role_name')
email: user.email!, .eq('id', roleId)
role: 'WARGA', // Default role .single();
roleName = roleResponse['role_name'];
} catch (e) {
print('Error mengambil role dari tabel roles: $e');
return null;
}
}
print('Role: $roleName');
// Step 3: Ambil profil user berdasarkan role
Map<String, dynamic>? profileResponse;
dynamic roleData;
DesaModel? desa;
// Buat BaseUserModel
final baseUser = BaseUserModel(
id: userId,
email: user.email ?? '',
roleId: user.userMetadata?['role_id'],
roleName: roleName ?? '',
createdAt: DateTime.parse(user.createdAt),
updatedAt:
user.updatedAt != null ? DateTime.parse(user.updatedAt!) : null,
); );
print('DEBUG: User model default dibuat: ${_cachedUser?.email}');
return _cachedUser; if ((roleName ?? '').toLowerCase() == 'warga') {
profileResponse = await _supabaseService.client
.from('warga')
.select(
'*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId)
.single();
if (profileResponse != null) {
// Ekstrak data desa jika ada
if (profileResponse['desa'] != null) {
desa = DesaModel.fromJson(profileResponse['desa']);
print('Data Desa: ${desa.nama}');
}
roleData = WargaModel.fromJson(profileResponse);
print('Data Warga: ${roleData.namaLengkap}');
} else {
print('Tidak menemukan data warga untuk ID: $userId');
return null;
}
} else if (roleName.toLowerCase() == 'petugas_desa') {
profileResponse = await _supabaseService.client
.from('petugas_desa')
.select(
'*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId)
.single();
if (profileResponse != null) {
// Ekstrak data desa jika ada
if (profileResponse['desa'] != null) {
desa = DesaModel.fromJson(profileResponse['desa']);
print('Data Desa: ${desa.nama}');
}
roleData = PetugasDesaModel.fromJson(profileResponse);
}
} else if (roleName.toLowerCase() == 'donatur') {
profileResponse = await _supabaseService.client
.from('donatur')
.select('*')
.eq('id', userId)
.single();
if (profileResponse != null) {
roleData = DonaturModel.fromJson(profileResponse);
}
}
if (roleData == null) {
print('Tidak menemukan data profil untuk role: $roleName');
return null;
}
// Perbarui baseUser dengan data desa jika ada
final updatedBaseUser = BaseUserModel(
id: baseUser.id,
email: baseUser.email,
roleId: baseUser.roleId,
roleName: baseUser.roleName,
createdAt: baseUser.createdAt,
updatedAt: baseUser.updatedAt,
desa: desa, // Set desa dari data yang diambil
);
// Simpan cache user data
final userData = UserData(
baseUser: updatedBaseUser,
roleData: roleData,
);
_cachedUserData = userData;
return userData;
} catch (e) { } catch (e) {
print('Error pada getCurrentUser: $e'); print('Error pada getCurrentUser: $e');
// Jika terjadi error, kembalikan model dengan data minimal
_cachedUser = UserModel(
id: user.id,
email: user.email!,
role: 'WARGA', // Default role
);
return _cachedUser;
} }
} }
return null; return null;
@ -115,31 +335,49 @@ class AuthProvider {
return await _supabaseService.getWargaByUserId(); return await _supabaseService.getWargaByUserId();
} }
// Metode untuk membuat profil warga // // Metode untuk membuat profil donatur
Future<void> createWargaProfile({ // Future<void> createDonaturProfile({
required String nik, // required String namaLengkap,
required String namaLengkap, // String? instansi,
required String jenisKelamin, // String? jabatan,
String? noHp, // String? noHp,
String? alamat, // String? alamat,
String? tempatLahir, // String? desaId,
DateTime? tanggalLahir, // }) async {
String? agama, // await _supabaseService.createDonaturProfile(
}) async { // namaLengkap: namaLengkap,
await _supabaseService.createWargaProfile( // instansi: instansi,
nik: nik, // jabatan: jabatan,
namaLengkap: namaLengkap, // noHp: noHp,
jenisKelamin: jenisKelamin, // alamat: alamat,
noHp: noHp, // desaId: desaId,
alamat: alamat, // );
tempatLahir: tempatLahir,
tanggalLahir: tanggalLahir,
agama: agama,
);
// Invalidasi cache setelah membuat profil baru // // Invalidasi cache setelah membuat profil baru
_cachedUser = null; // _cachedUserData = null;
} // }
// // Metode untuk membuat profil petugas desa
// Future<void> createPetugasDesaProfile({
// required String namaLengkap,
// String? nip,
// String? jabatan,
// String? noHp,
// String? alamat,
// String? desaId,
// }) async {
// await _supabaseService.createPetugasDesaProfile(
// namaLengkap: namaLengkap,
// nip: nip,
// jabatan: jabatan,
// noHp: noHp,
// alamat: alamat,
// desaId: desaId,
// );
// // Invalidasi cache setelah membuat profil baru
// _cachedUserData = null;
// }
// Metode untuk mendapatkan notifikasi pengguna // Metode untuk mendapatkan notifikasi pengguna
Future<List<Map<String, dynamic>>> getUserNotifications( Future<List<Map<String, dynamic>>> getUserNotifications(

View File

@ -1,17 +1,51 @@
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/data/models/donatur_model.dart';
import 'package:penyaluran_app/app/data/models/petugas_desa_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart'; import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/warga_model.dart';
import 'package:penyaluran_app/app/data/providers/auth_provider.dart'; import 'package:penyaluran_app/app/data/providers/auth_provider.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart'; import 'package:penyaluran_app/app/routes/app_pages.dart';
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
class AuthController extends GetxController { class AuthController extends GetxController {
static AuthController get to => Get.find(); static AuthController get to => Get.find();
final AuthProvider _authProvider = AuthProvider(); final AuthProvider _authProvider = AuthProvider();
final Rx<UserModel?> _user = Rx<UserModel?>(null); final Rx<UserData?> _userData = Rx<UserData?>(null);
UserModel? get user => _user.value; UserData? get userData => _userData.value;
// Getter untuk BaseUserModel
BaseUserModel? get baseUser => _userData.value?.baseUser;
// Getter untuk role
String get role => _userData.value?.baseUser.roleName.toLowerCase() ?? '';
// Getter dinamis untuk data role-specific
dynamic get roleData => _userData.value?.roleData;
// Getter untuk memeriksa tipe roleData
bool get isWarga =>
_userData.value?.roleData is WargaModel && role == 'warga';
bool get isDonatur =>
_userData.value?.roleData is DonaturModel && role == 'donatur';
bool get isPetugasDesa =>
_userData.value?.roleData is PetugasDesaModel && role == 'petugas_desa';
// Helper method untuk mendapatkan nama display
String get displayName {
if (roleData == null) return baseUser?.email ?? 'Pengguna';
if (isWarga) {
return (roleData as WargaModel).displayName;
} else if (isDonatur) {
return (roleData as DonaturModel).displayName;
} else if (isPetugasDesa) {
return (roleData as PetugasDesaModel).displayName;
}
return baseUser?.email ?? 'Pengguna';
}
final RxBool isLoading = false.obs; final RxBool isLoading = false.obs;
final RxBool isWargaProfileComplete = false.obs; final RxBool isWargaProfileComplete = false.obs;
@ -74,21 +108,21 @@ class AuthController extends GetxController {
print('Memeriksa status autentikasi...'); print('Memeriksa status autentikasi...');
// Jika user sudah ada di memori dan profil sudah diambil, gunakan data yang ada // Jika user sudah ada di memori dan profil sudah diambil, gunakan data yang ada
if (_user.value != null && _hasLoadedProfile.value) { if (_userData.value != null && _hasLoadedProfile.value) {
print('Menggunakan data user yang sudah ada di memori'); print('Menggunakan data user yang sudah ada di memori');
_handleAuthenticatedUser(_user.value!); _handleAuthenticatedUser(_userData.value!);
return; return;
} }
// Jika belum ada data user, ambil dari provider // Jika belum ada data user, ambil dari provider
final currentUser = await _authProvider.getCurrentUser(); final currentUserData = await _authProvider.getCurrentUser();
if (currentUser != null) { if (currentUserData != null) {
print( print(
'User terautentikasi: ${currentUser.email}, role: ${currentUser.role}'); 'User terautentikasi: ${currentUserData.baseUser.email}, role: ${currentUserData.baseUser.roleName}');
_user.value = currentUser; _userData.value = currentUserData;
_hasLoadedProfile.value = true; _hasLoadedProfile.value = true;
_handleAuthenticatedUser(currentUser); _handleAuthenticatedUser(currentUserData);
} else { } else {
print('Tidak ada user yang terautentikasi'); print('Tidak ada user yang terautentikasi');
_handleUnauthenticatedUser(); _handleUnauthenticatedUser();
@ -104,13 +138,13 @@ class AuthController extends GetxController {
} }
// Metode untuk menangani user yang terautentikasi // Metode untuk menangani user yang terautentikasi
void _handleAuthenticatedUser(UserModel user) { void _handleAuthenticatedUser(UserData userData) {
// Hindari navigasi jika sudah berada di halaman yang sesuai // Hindari navigasi jika sudah berada di halaman yang sesuai
final currentRoute = Get.currentRoute; final currentRoute = Get.currentRoute;
print('Rute saat ini: $currentRoute'); print('Rute saat ini: $currentRoute');
// Pastikan role tidak null, gunakan default jika null // Dapatkan role dari BaseUserModel
final role = user.role.isNotEmpty ? user.role : 'WARGA'; final role = userData.baseUser.roleName.toLowerCase();
print('Role yang digunakan: $role'); print('Role yang digunakan: $role');
// Untuk semua role, arahkan ke dashboard masing-masing // Untuk semua role, arahkan ke dashboard masing-masing
@ -146,9 +180,8 @@ class AuthController extends GetxController {
// Memeriksa status profil warga // Memeriksa status profil warga
Future<void> checkWargaProfileStatus() async { Future<void> checkWargaProfileStatus() async {
try { try {
if (_user.value?.role == 'WARGA') { if (role == 'warga') {
final wargaData = await _authProvider.getWargaData(); isWargaProfileComplete.value = roleData != null;
isWargaProfileComplete.value = wargaData != null;
} else { } else {
isWargaProfileComplete.value = true; isWargaProfileComplete.value = true;
} }
@ -177,17 +210,14 @@ class AuthController extends GetxController {
// Bersihkan dependensi form sebelum navigasi // Bersihkan dependensi form sebelum navigasi
clearFormDependencies(); clearFormDependencies();
switch (role) { switch (role.toLowerCase()) {
case 'WARGA': case 'warga':
Get.offAllNamed(Routes.wargaDashboard); Get.offAllNamed(Routes.wargaDashboard);
break; break;
case 'PETUGASVERIFIKASI': case 'petugas_desa':
Get.offAllNamed(Routes.petugasVerifikasiDashboard);
break;
case 'PETUGASDESA':
Get.offAllNamed(Routes.petugasDesaDashboard); Get.offAllNamed(Routes.petugasDesaDashboard);
break; break;
case 'DONATUR': case 'donatur':
Get.offAllNamed(Routes.donaturDashboard); Get.offAllNamed(Routes.donaturDashboard);
break; break;
default: default:
@ -229,88 +259,81 @@ class AuthController extends GetxController {
isLoading.value = true; isLoading.value = true;
print('DEBUG: Memanggil _authProvider.signIn'); print('DEBUG: Memanggil _authProvider.signIn');
final user = await _authProvider.signIn( final userData = await _authProvider.signIn(
email, email,
password, password,
); );
print('DEBUG: Hasil signIn: ${user != null ? 'Berhasil' : 'Gagal'}'); print('DEBUG: Hasil signIn: ${userData != null ? 'Berhasil' : 'Gagal'}');
if (user != null) { if (userData != null) {
print('DEBUG: User ditemukan, role: ${user.role}'); print('DEBUG: User ditemukan, role: ${userData.baseUser.roleName}');
_user.value = user; _userData.value = userData;
_hasLoadedProfile.value = true; // Tandai bahwa profil sudah diambil _hasLoadedProfile.value = true; // Tandai bahwa profil sudah diambil
clearControllers(); clearControllers();
// Arahkan ke dashboard sesuai peran // Arahkan ke dashboard sesuai peran
print('DEBUG: Navigasi berdasarkan peran: ${user.role}'); print(
navigateBasedOnRole(user.role); 'DEBUG: Navigasi berdasarkan peran: ${userData.baseUser.roleName}');
navigateBasedOnRole(userData.baseUser.roleName.toLowerCase());
} else { } else {
print('DEBUG: User null setelah login berhasil'); print('DEBUG: User null setelah login berhasil');
handleLoginError(Exception('Data pengguna tidak ditemukan'));
} }
} catch (e) { } catch (e) {
print('DEBUG: Error detail pada login: $e'); handleLoginError(e);
print('DEBUG: Stack trace: ${StackTrace.current}');
Get.snackbar(
'Error',
'Login gagal: ${e.toString()}',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally { } finally {
print('DEBUG: Mengatur isLoading ke false'); print('DEBUG: Mengatur isLoading ke false');
isLoading.value = false; isLoading.value = false;
} }
} }
// Metode untuk logout // Menangani error saat login
Future<void> logout() async { void handleLoginError(dynamic error) {
try { print('DEBUG: Error login: $error');
// Ambil semua controller yang mungkin perlu dibersihkan
try { String errorMessage = 'Terjadi kesalahan saat login. Silakan coba lagi.';
final wargaController = Get.find<WargaDashboardController>();
wargaController.penerimaPenyaluran.clear(); if (error.toString().contains('Invalid login credentials')) {
wargaController.pengajuanKelayakan.clear(); errorMessage = 'Email atau password salah. Silakan coba lagi.';
wargaController.pengaduan.clear(); } else if (error.toString().contains('Too many requests')) {
} catch (e) { errorMessage = 'Terlalu banyak percobaan login. Silakan coba lagi nanti.';
// Jika controller tidak ditemukan, abaikan
print('Controller tidak ditemukan: $e');
} }
// Logout dari Supabase
await _authProvider.signOut();
// Reset semua state
_user.value = null;
_hasLoadedProfile.value = false;
isWargaProfileComplete.value = false;
// Bersihkan dependensi form sebelum navigasi
clearFormDependencies();
// Navigasi ke halaman login
Get.offAllNamed(Routes.login);
} catch (e) {
Get.snackbar( Get.snackbar(
'Error', 'Error',
'Logout gagal: ${e.toString()}', errorMessage,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red, backgroundColor: Colors.red,
colorText: Colors.white, colorText: Colors.white,
); );
} }
}
// Metode untuk membersihkan controller // Metode untuk membersihkan controller
void clearControllers() { void clearControllers() {
try { emailController.clear();
if (emailController.text.isNotEmpty) emailController.clear(); passwordController.clear();
if (passwordController.text.isNotEmpty) passwordController.clear();
if (confirmPasswordController.text.isNotEmpty) {
confirmPasswordController.clear(); confirmPasswordController.clear();
} }
// Metode untuk logout
Future<void> logout() async {
try {
isLoading.value = true;
await _authProvider.signOut();
_userData.value = null;
_hasLoadedProfile.value = false;
Get.offAllNamed(Routes.login);
} catch (e) { } catch (e) {
print('Error clearing controllers: $e'); print('Error saat logout: $e');
Get.snackbar(
'Error',
'Terjadi kesalahan saat logout. Silakan coba lagi.',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
} }
} }
@ -355,16 +378,42 @@ class AuthController extends GetxController {
} }
} }
// Metode untuk refresh data user setelah update profil
Future<void> refreshUserData() async {
try {
print('Memperbarui data pengguna...');
isLoading.value = true;
// Hapus cache profil yang sudah tidak valid
_hasLoadedProfile.value = false;
// Ambil data user terbaru dari provider dengan menskip cache
final currentUserData =
await _authProvider.getCurrentUser(skipCache: true);
if (currentUserData != null) {
print(
'Data pengguna berhasil diperbarui: ${currentUserData.baseUser.name}');
_userData.value = currentUserData;
_hasLoadedProfile.value = true;
} else {
print('Gagal memperbarui data pengguna');
}
} catch (e) {
print('Error refreshing user data: $e');
} finally {
isLoading.value = false;
}
}
// Mendapatkan rute target berdasarkan role // Mendapatkan rute target berdasarkan role
String _getTargetRouteForRole(String role) { String _getTargetRouteForRole(String role) {
switch (role) { switch (role.toLowerCase()) {
case 'WARGA': case 'warga':
return Routes.wargaDashboard; return Routes.wargaDashboard;
case 'PETUGASVERIFIKASI': case 'petugas_desa':
return Routes.petugasVerifikasiDashboard;
case 'PETUGASDESA':
return Routes.petugasDesaDashboard; return Routes.petugasDesaDashboard;
case 'DONATUR': case 'donatur':
return Routes.donaturDashboard; return Routes.donaturDashboard;
default: default:
return Routes.home; return Routes.home;

View File

@ -34,6 +34,7 @@ class LaporanPenyaluranController extends GetxController {
final RxMap<String, dynamic> lokasiPenyaluran = RxMap<String, dynamic>(); final RxMap<String, dynamic> lokasiPenyaluran = RxMap<String, dynamic>();
final RxMap<String, dynamic> desaData = RxMap<String, dynamic>(); final RxMap<String, dynamic> desaData = RxMap<String, dynamic>();
final RxMap<String, dynamic> kategoriBantuan = RxMap<String, dynamic>(); final RxMap<String, dynamic> kategoriBantuan = RxMap<String, dynamic>();
final RxMap<String, dynamic> petugasData = RxMap<String, dynamic>();
// Form controllers // Form controllers
final TextEditingController judulController = TextEditingController(); final TextEditingController judulController = TextEditingController();
@ -53,8 +54,8 @@ class LaporanPenyaluranController extends GetxController {
final RxString filterStatus = 'SEMUA'.obs; final RxString filterStatus = 'SEMUA'.obs;
// Getter untuk data user // Getter untuk data user
get user => _authController.user; dynamic get user => _authController.baseUser;
String get role => user?.role ?? 'WARGA'; String get role => _authController.role;
@override @override
void onInit() { void onInit() {
@ -168,6 +169,8 @@ class LaporanPenyaluranController extends GetxController {
.single(); .single();
selectedPenyaluran.value = PenyaluranBantuanModel.fromJson(response); selectedPenyaluran.value = PenyaluranBantuanModel.fromJson(response);
print(
'PenyaluranDetail - petugasId: ${selectedPenyaluran.value?.petugasId}');
// Ambil data penerima terkait // Ambil data penerima terkait
await fetchPenerimaPenyaluran(penyaluranId); await fetchPenerimaPenyaluran(penyaluranId);
@ -178,6 +181,11 @@ class LaporanPenyaluranController extends GetxController {
selectedPenyaluran.value!.lokasiPenyaluranId!); selectedPenyaluran.value!.lokasiPenyaluranId!);
} }
// Ambil data petugas jika ada
if (selectedPenyaluran.value?.petugasId != null) {
await fetchPetugasData(selectedPenyaluran.value!.petugasId!);
}
// Hitung penggunaan stok bantuan // Hitung penggunaan stok bantuan
await calculateStokBantuanUsage(penyaluranId); await calculateStokBantuanUsage(penyaluranId);
} catch (e) { } catch (e) {
@ -251,6 +259,22 @@ class LaporanPenyaluranController extends GetxController {
} }
} }
// Mendapatkan data petugas
Future<void> fetchPetugasData(String petugasId) async {
try {
final response = await _supabaseService.client
.from('petugas_desa')
.select('*, desa(nama)')
.eq('id', petugasId)
.single();
petugasData.value = response;
print('Data petugas berhasil diambil: $petugasData');
} catch (e) {
print('Error fetching petugas data: $e');
}
}
// Menghitung penggunaan stok bantuan // Menghitung penggunaan stok bantuan
Future<void> calculateStokBantuanUsage(String penyaluranId) async { Future<void> calculateStokBantuanUsage(String penyaluranId) async {
try { try {
@ -563,7 +587,8 @@ class LaporanPenyaluranController extends GetxController {
// Load logo - tidak perlu menampilkan error jika logo tidak ada // Load logo - tidak perlu menampilkan error jika logo tidak ada
pw.MemoryImage? logoImage; pw.MemoryImage? logoImage;
try { try {
final logoBytes = await rootBundle.load('assets/img/logo.png'); final logoBytes =
await rootBundle.load('assets/images/penyaluran-icon.png');
logoImage = pw.MemoryImage(logoBytes.buffer.asUint8List()); logoImage = pw.MemoryImage(logoBytes.buffer.asUint8List());
} catch (e) { } catch (e) {
// Logo tidak ditemukan - tidak perlu print error // Logo tidak ditemukan - tidak perlu print error
@ -600,7 +625,7 @@ class LaporanPenyaluranController extends GetxController {
children: [ children: [
logoImage != null logoImage != null
? pw.Image(logoImage, width: 60, height: 60) ? pw.Image(logoImage, width: 60, height: 60)
: pw.SizedBox(width: 60), : pw.SizedBox(width: 10, height: 10),
pw.Column( pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end, crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [ children: [
@ -924,10 +949,7 @@ class LaporanPenyaluranController extends GetxController {
isHeader: true, isHeader: true,
align: pw.TextAlign.center, align: pw.TextAlign.center,
color: PdfColors.blue900), color: PdfColors.blue900),
_buildPdfTableCell('Satuan', ttfBold,
isHeader: true,
align: pw.TextAlign.center,
color: PdfColors.blue900),
_buildPdfTableCell('Status', ttfBold, _buildPdfTableCell('Status', ttfBold,
isHeader: true, isHeader: true,
align: pw.TextAlign.center, align: pw.TextAlign.center,
@ -947,13 +969,19 @@ class LaporanPenyaluranController extends GetxController {
? '${penerima.jumlahBantuan} ${penerima.satuan ?? ''}' ? '${penerima.jumlahBantuan} ${penerima.satuan ?? ''}'
: '-'; : '-';
final isUang =
penerima.satuan?.toLowerCase() == 'rupiah';
final jumlahBantuan = penerima.jumlahBantuan ?? 0;
final formattedJumlah = isUang
? 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(jumlahBantuan)}'
: '$jumlahBantuan ${penerima.satuan ?? ''}';
return pw.TableRow( return pw.TableRow(
children: [ children: [
_buildPdfTableCell(nik, ttf), _buildPdfTableCell(nik, ttf),
_buildPdfTableCell(wargaNama, ttf), _buildPdfTableCell(wargaNama, ttf),
_buildPdfTableCell(jumlah, ttf, _buildPdfTableCell(formattedJumlah, ttf,
align: pw.TextAlign.center),
_buildPdfTableCell(penerima.satuan ?? '-', ttf,
align: pw.TextAlign.center), align: pw.TextAlign.center),
_buildPdfTableCell( _buildPdfTableCell(
penerima.statusPenerimaan ?? '-', ttf, penerima.statusPenerimaan ?? '-', ttf,
@ -1005,12 +1033,17 @@ class LaporanPenyaluranController extends GetxController {
'Dokumentasi dapat diakses melalui tautan berikut:', 'Dokumentasi dapat diakses melalui tautan berikut:',
style: pw.TextStyle(font: ttf)), style: pw.TextStyle(font: ttf)),
pw.SizedBox(height: 3), pw.SizedBox(height: 3),
pw.Text(laporan.dokumentasiUrl!, pw.UrlLink(
destination: laporan.dokumentasiUrl!,
child: pw.Text(
laporan.dokumentasiUrl!,
style: pw.TextStyle( style: pw.TextStyle(
font: ttf, font: ttf,
color: PdfColors.blue, color: PdfColors.blue,
decoration: pw.TextDecoration.underline, decoration: pw.TextDecoration.underline,
)), ),
),
),
if (laporan.beritaAcaraUrl != null) if (laporan.beritaAcaraUrl != null)
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
], ],
@ -1024,12 +1057,17 @@ class LaporanPenyaluranController extends GetxController {
'Berita acara dapat diakses melalui tautan berikut:', 'Berita acara dapat diakses melalui tautan berikut:',
style: pw.TextStyle(font: ttf)), style: pw.TextStyle(font: ttf)),
pw.SizedBox(height: 3), pw.SizedBox(height: 3),
pw.Text(laporan.beritaAcaraUrl!, pw.UrlLink(
destination: laporan.beritaAcaraUrl!,
child: pw.Text(
laporan.beritaAcaraUrl!,
style: pw.TextStyle( style: pw.TextStyle(
font: ttf, font: ttf,
color: PdfColors.blue, color: PdfColors.blue,
decoration: pw.TextDecoration.underline, decoration: pw.TextDecoration.underline,
)), ),
),
),
], ],
], ],
ttfBold, ttfBold,
@ -1046,7 +1084,9 @@ class LaporanPenyaluranController extends GetxController {
crossAxisAlignment: pw.CrossAxisAlignment.center, crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [ children: [
pw.Text( pw.Text(
'Penanggung Jawab,', petugasData.isNotEmpty
? 'Petugas ${petugasData['desa'] != null ? "Desa " + (petugasData['desa']['nama'] ?? '') : ''}'
: 'Penanggung Jawab,',
style: pw.TextStyle(font: ttf), style: pw.TextStyle(font: ttf),
), ),
pw.SizedBox(height: 50), // Ruang untuk tanda tangan pw.SizedBox(height: 50), // Ruang untuk tanda tangan
@ -1057,12 +1097,23 @@ class LaporanPenyaluranController extends GetxController {
top: pw.BorderSide(color: PdfColors.black))), top: pw.BorderSide(color: PdfColors.black))),
padding: const pw.EdgeInsets.only(top: 5), padding: const pw.EdgeInsets.only(top: 5),
child: pw.Text( child: pw.Text(
// Sesuaikan dengan properti yang ada di model user // Gunakan data petugas dari penyaluran, jika tidak ada gunakan data user
user?.email ?? 'Admin Sistem', petugasData.isNotEmpty
? petugasData['nama_lengkap'] ?? 'Petugas Desa'
: user?.nama ?? 'Admin Sistem',
style: pw.TextStyle(font: ttfBold), style: pw.TextStyle(font: ttfBold),
textAlign: pw.TextAlign.center, textAlign: pw.TextAlign.center,
), ),
), ),
// NIP atau nomor identitas petugas
pw.Text(
petugasData.isNotEmpty
? petugasData['nip'] ?? '-'
: user?.nip ?? '-',
style: pw.TextStyle(font: ttf),
textAlign: pw.TextAlign.center,
),
], ],
), ),
], ],

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:penyaluran_app/app/modules/laporan_penyaluran/controllers/laporan_penyaluran_controller.dart'; import 'package:penyaluran_app/app/modules/laporan_penyaluran/controllers/laporan_penyaluran_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';
import 'package:penyaluran_app/app/widgets/custom_app_bar.dart'; import 'package:penyaluran_app/app/widgets/custom_app_bar.dart';
import 'package:penyaluran_app/app/widgets/section_header.dart'; import 'package:penyaluran_app/app/widgets/section_header.dart';
import 'package:penyaluran_app/app/widgets/status_badge.dart'; import 'package:penyaluran_app/app/widgets/status_badge.dart';
@ -185,15 +186,15 @@ class LaporanPenyaluranDetailView extends GetView<LaporanPenyaluranController> {
_buildInfoRow( _buildInfoRow(
'Tanggal Penyaluran', 'Tanggal Penyaluran',
penyaluran.tanggalPenyaluran != null penyaluran.tanggalPenyaluran != null
? DateFormat('dd/MM/yyyy') ? DateTimeHelper.formatDateTime(
.format(penyaluran.tanggalPenyaluran!) penyaluran.tanggalPenyaluran!)
: '-', : '-',
), ),
_buildInfoRow( _buildInfoRow(
'Tanggal Selesai', 'Tanggal Selesai',
penyaluran.tanggalSelesai != null penyaluran.tanggalSelesai != null
? DateFormat('dd/MM/yyyy') ? DateTimeHelper.formatDateTime(
.format(penyaluran.tanggalSelesai!) penyaluran.tanggalSelesai!)
: '-', : '-',
), ),
_buildInfoRow('Jumlah Penerima', _buildInfoRow('Jumlah Penerima',
@ -525,7 +526,7 @@ class LaporanPenyaluranDetailView extends GetView<LaporanPenyaluranController> {
Expanded( Expanded(
flex: 3, flex: 3,
child: Text( child: Text(
'Nama Penerima', 'NIK',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ),
@ -533,7 +534,7 @@ class LaporanPenyaluranDetailView extends GetView<LaporanPenyaluranController> {
Expanded( Expanded(
flex: 3, flex: 3,
child: Text( child: Text(
'Jenis Bantuan', 'Nama Penerima',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ),
@ -568,12 +569,13 @@ class LaporanPenyaluranDetailView extends GetView<LaporanPenyaluranController> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final penerima = final penerima =
controller.daftarPenerima[index]; controller.daftarPenerima[index];
final wargaNik = penerima.warga != null
? penerima.warga!['nik'] ?? '-'
: '-';
final wargaNama = penerima.warga != null final wargaNama = penerima.warga != null
? penerima.warga!['nama_lengkap'] ?? '-' ? penerima.warga!['nama_lengkap'] ?? '-'
: '-'; : '-';
final stokNama = penerima.stokBantuan != null
? penerima.stokBantuan!['nama'] ?? '-'
: '-';
final jumlah = penerima.jumlahBantuan != null final jumlah = penerima.jumlahBantuan != null
? '${penerima.jumlahBantuan} ${penerima.satuan ?? ''}' ? '${penerima.jumlahBantuan} ${penerima.satuan ?? ''}'
: '-'; : '-';
@ -591,11 +593,11 @@ class LaporanPenyaluranDetailView extends GetView<LaporanPenyaluranController> {
children: [ children: [
Expanded( Expanded(
flex: 3, flex: 3,
child: Text(wargaNama), child: Text(wargaNik),
), ),
Expanded( Expanded(
flex: 3, flex: 3,
child: Text(stokNama), child: Text(wargaNama),
), ),
Expanded( Expanded(
flex: 2, flex: 2,
@ -867,9 +869,7 @@ class LaporanPenyaluranDetailView extends GetView<LaporanPenyaluranController> {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
tanggalLaporan != null DateTimeHelper.formatDateTime(tanggalLaporan),
? DateFormat('dd/MM/yyyy').format(tanggalLaporan)
: '-',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -54,12 +54,12 @@ class DetailPenyaluranController extends GetxController {
final user = _supabaseService.client.auth.currentUser; final user = _supabaseService.client.auth.currentUser;
if (user != null) { if (user != null) {
final userData = await _supabaseService.client final userData = await _supabaseService.client
.from('users') .from('user_profile')
.select('role') .select('role')
.eq('id', user.id) .eq('id', user.id)
.single(); .single();
if (userData['role'] == 'petugas_desa') { if (userData['role'] == 'PETUGASDESA') {
isPetugasDesa.value = true; isPetugasDesa.value = true;
} }
} }

View File

@ -275,7 +275,7 @@ class DonaturController extends GetxController {
try { try {
final donatur = daftarDonatur.firstWhere((d) => d.id == donaturId); final donatur = daftarDonatur.firstWhere((d) => d.id == donaturId);
return donatur.nama; return donatur.namaLengkap;
} catch (e) { } catch (e) {
return null; return null;
} }

View File

@ -47,7 +47,7 @@ class JadwalPenyaluranController extends GetxController {
// Controller untuk pencarian // Controller untuk pencarian
final TextEditingController searchController = TextEditingController(); final TextEditingController searchController = TextEditingController();
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
@override @override
void onInit() { void onInit() {

View File

@ -32,7 +32,7 @@ class PenerimaBantuanController extends GetxController {
// Form key // Form key
final GlobalKey<FormState> penerimaFormKey = GlobalKey<FormState>(); final GlobalKey<FormState> penerimaFormKey = GlobalKey<FormState>();
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
@override @override
void onInit() { void onInit() {
@ -79,19 +79,21 @@ class PenerimaBantuanController extends GetxController {
isLoading.value = true; isLoading.value = true;
try { try {
final penerima = WargaModel( // Biarkan Supabase yang akan menghasilkan ID saat insert
nama: namaController.text, // Kita hanya perlu menyediakan data lainnya
nik: nikController.text, final penerima = {
alamat: alamatController.text, 'nama_lengkap': namaController.text,
telepon: teleponController.text, 'nik': nikController.text,
email: emailController.text, 'alamat': alamatController.text,
catatan: catatanController.text, 'no_hp': teleponController.text,
status: 'AKTIF', 'email': emailController.text,
createdAt: DateTime.now(), 'catatan': catatanController.text,
updatedAt: DateTime.now(), 'status': 'AKTIF',
); 'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
};
await _supabaseService.tambahPenerima(penerima.toJson()); await _supabaseService.tambahPenerima(penerima);
// Clear form // Clear form
clearForm(); clearForm();
@ -125,18 +127,17 @@ class PenerimaBantuanController extends GetxController {
isLoading.value = true; isLoading.value = true;
try { try {
final penerima = WargaModel( final penerima = {
id: penerimaId, 'nama_lengkap': namaController.text,
nama: namaController.text, 'nik': nikController.text,
nik: nikController.text, 'alamat': alamatController.text,
alamat: alamatController.text, 'no_hp': teleponController.text,
telepon: teleponController.text, 'email': emailController.text,
email: emailController.text, 'catatan': catatanController.text,
catatan: catatanController.text, 'updated_at': DateTime.now().toIso8601String(),
updatedAt: DateTime.now(), };
);
await _supabaseService.updatePenerima(penerimaId, penerima.toJson()); await _supabaseService.updatePenerima(penerimaId, penerima);
// Clear form // Clear form
clearForm(); clearForm();
@ -218,10 +219,10 @@ class PenerimaBantuanController extends GetxController {
} }
void setFormData(WargaModel penerima) { void setFormData(WargaModel penerima) {
namaController.text = penerima.nama ?? ''; namaController.text = penerima.namaLengkap ?? '';
nikController.text = penerima.nik ?? ''; nikController.text = penerima.nik ?? '';
alamatController.text = penerima.alamat ?? ''; alamatController.text = penerima.alamat ?? '';
teleponController.text = penerima.telepon ?? ''; teleponController.text = penerima.noHp ?? '';
emailController.text = penerima.email ?? ''; emailController.text = penerima.email ?? '';
catatanController.text = penerima.catatan ?? ''; catatanController.text = penerima.catatan ?? '';
} }

View File

@ -37,7 +37,7 @@ class PengaduanController extends GetxController {
// Image picker // Image picker
final ImagePicker _imagePicker = ImagePicker(); final ImagePicker _imagePicker = ImagePicker();
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
@override @override
void onInit() { void onInit() {

View File

@ -53,7 +53,7 @@ class PenitipanBantuanController extends GetxController {
// Tambahkan properti untuk waktu terakhir update // Tambahkan properti untuk waktu terakhir update
final Rx<DateTime> lastUpdateTime = DateTime.now().obs; final Rx<DateTime> lastUpdateTime = DateTime.now().obs;
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
// Getter untuk counter dari CounterService // Getter untuk counter dari CounterService
RxInt get jumlahMenunggu => _counterService.jumlahMenunggu; RxInt get jumlahMenunggu => _counterService.jumlahMenunggu;
@ -72,10 +72,9 @@ class PenitipanBantuanController extends GetxController {
loadPenitipanData(); loadPenitipanData();
loadKategoriBantuanData(); loadKategoriBantuanData();
// Tambahkan delay untuk memastikan data petugas desa dimuat setelah penitipan
Future.delayed(const Duration(seconds: 1), () { // Hapus delay dan muat data petugas desa langsung
loadAllPetugasDesaData(); loadAllPetugasDesaData();
});
// Listener untuk pencarian donatur // Listener untuk pencarian donatur
donaturSearchController.addListener(() { donaturSearchController.addListener(() {
@ -123,25 +122,24 @@ class PenitipanBantuanController extends GetxController {
// Muat informasi petugas desa untuk item yang terverifikasi // Muat informasi petugas desa untuk item yang terverifikasi
print( print(
'Memuat informasi petugas desa untuk ${daftarPenitipan.length} penitipan'); 'Memuat informasi petugas desa untuk ${daftarPenitipan.length} penitipan');
List<Future> petugasLoaders = [];
for (var item in daftarPenitipan) { for (var item in daftarPenitipan) {
if (item.status == 'TERVERIFIKASI' && item.petugasDesaId != null) { if (item.status == 'TERVERIFIKASI' && item.petugasDesaId != null) {
print( print(
'Memuat informasi petugas desa untuk penitipan ID: ${item.id}, petugasDesaId: ${item.petugasDesaId}'); 'Memuat informasi petugas desa untuk penitipan ID: ${item.id}, petugasDesaId: ${item.petugasDesaId}');
final petugasData = await getPetugasDesaInfo(item.petugasDesaId); petugasLoaders.add(getPetugasDesaInfo(item.petugasDesaId));
if (petugasData != null) {
print(
'Berhasil memuat data petugas desa: ${petugasData['name']} untuk ID: ${item.petugasDesaId}');
} else {
print(
'Gagal memuat data petugas desa untuk ID: ${item.petugasDesaId}');
}
} }
} }
// Tunggu semua data petugas desa selesai dimuat
await Future.wait(petugasLoaders);
// Debug: print semua data petugas desa yang sudah dimuat // Debug: print semua data petugas desa yang sudah dimuat
print('Data petugas desa yang sudah dimuat:'); print('Data petugas desa yang sudah dimuat:');
petugasDesaCache.forEach((key, value) { petugasDesaCache.forEach((key, value) {
print('ID: $key, Nama: $value'); print('ID: $key, Nama: ${value['nama_lengkap']}');
}); });
// Update waktu terakhir refresh // Update waktu terakhir refresh
@ -490,7 +488,8 @@ class PenitipanBantuanController extends GetxController {
final donaturId = item.donaturId; final donaturId = item.donaturId;
String donaturNama = ''; String donaturNama = '';
if (donaturId != null && donaturCache.containsKey(donaturId)) { if (donaturId != null && donaturCache.containsKey(donaturId)) {
donaturNama = donaturCache[donaturId]?.nama?.toLowerCase() ?? ''; donaturNama =
donaturCache[donaturId]?.namaLengkap?.toLowerCase() ?? '';
} }
final donaturMatch = donaturNama.contains(searchText); final donaturMatch = donaturNama.contains(searchText);
@ -551,25 +550,37 @@ class PenitipanBantuanController extends GetxController {
if (!petugasDesaCache.containsKey(petugasDesaId)) { if (!petugasDesaCache.containsKey(petugasDesaId)) {
print( print(
'Data petugas desa tidak ditemukan di cache untuk ID: $petugasDesaId'); 'Data petugas desa tidak ditemukan di cache untuk ID: $petugasDesaId');
// Jadwalkan pengambilan data untuk nanti // Muat data petugas dan perbarui UI
loadPetugasDesaData(petugasDesaId); loadPetugasDesaData(petugasDesaId);
return 'Memuat...';
// Coba cek lagi setelah pemuatan
if (petugasDesaCache.containsKey(petugasDesaId)) {
// Akses nama dari struktur data petugas_desa
final nama = petugasDesaCache[petugasDesaId]?['nama_lengkap'];
return nama ?? 'Tidak diketahui';
}
return 'Memuat data...';
} }
// Sekarang data seharusnya ada di cache // Sekarang data seharusnya ada di cache
final nama = petugasDesaCache[petugasDesaId]?['name']; // Akses nama dari struktur data petugas_desa
final nama = petugasDesaCache[petugasDesaId]?['nama_lengkap'];
print('Nama petugas desa: $nama untuk ID: $petugasDesaId'); print('Nama petugas desa: $nama untuk ID: $petugasDesaId');
return nama ?? 'Tidak diketahui'; return nama ?? 'Tidak diketahui';
} }
// Fungsi untuk memuat data petugas desa dan memperbarui UI // Fungsi untuk memuat data petugas desa dan memperbarui UI
void loadPetugasDesaData(String petugasDesaId) async { Future<void> loadPetugasDesaData(String petugasDesaId) async {
try { try {
print('Memuat data petugas desa untuk ID: $petugasDesaId');
final petugasData = await getPetugasDesaInfo(petugasDesaId); final petugasData = await getPetugasDesaInfo(petugasDesaId);
if (petugasData != null) { if (petugasData != null) {
// Data sudah dimasukkan ke cache oleh getPetugasDesaInfo // Data sudah dimasukkan ke cache oleh getPetugasDesaInfo
// Refresh UI print('Berhasil memuat data petugas: ${petugasData['nama_lengkap']}');
update();
// Refresh UI segera
update(['petugas_data']);
} else { } else {
print( print(
'Gagal mengambil data petugas desa dari server untuk ID: $petugasDesaId'); 'Gagal mengambil data petugas desa dari server untuk ID: $petugasDesaId');
@ -597,7 +608,7 @@ class PenitipanBantuanController extends GetxController {
// Debug: print semua data petugas desa yang sudah dimuat // Debug: print semua data petugas desa yang sudah dimuat
print('Data petugas desa yang sudah dimuat setelah reload:'); print('Data petugas desa yang sudah dimuat setelah reload:');
petugasDesaCache.forEach((key, value) { petugasDesaCache.forEach((key, value) {
print('ID: $key, Nama: $value'); print('ID: $key, Nama: ${value['nama_lengkap']}');
}); });
} catch (e) { } catch (e) {
print('Error saat memuat ulang data petugas desa: $e'); print('Error saat memuat ulang data petugas desa: $e');
@ -643,15 +654,15 @@ class PenitipanBantuanController extends GetxController {
Future<String?> tambahDonatur({ Future<String?> tambahDonatur({
required String nama, required String nama,
required String telepon, required String noHp,
String? alamat, String? alamat,
String? email, String? email,
String? jenis, String? jenis,
}) async { }) async {
try { try {
final donaturData = { final donaturData = {
'nama': nama, 'nama_lengkap': nama,
'telepon': telepon, 'no_hp': noHp,
'alamat': alamat, 'alamat': alamat,
'email': email, 'email': email,
'jenis': jenis, 'jenis': jenis,

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:penyaluran_app/app/data/models/desa_model.dart'; import 'package:penyaluran_app/app/data/models/desa_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart'; import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/petugas_desa_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/modules/petugas_desa/controllers/counter_service.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/counter_service.dart';
import 'package:penyaluran_app/app/services/supabase_service.dart'; import 'package:penyaluran_app/app/services/supabase_service.dart';
@ -49,9 +50,57 @@ class PetugasDesaController extends GetxController {
// Variabel untuk pencarian dan filter // Variabel untuk pencarian dan filter
final searchQuery = ''.obs; final searchQuery = ''.obs;
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
String get role => user?.role ?? 'PETUGASDESA'; String get role => user?.roleName ?? 'PETUGAS_DESA';
String get nama => user?.name ?? 'Petugas Desa';
// Helper method untuk format role agar lebih rapi
String get formattedRole {
final roleText = role.toLowerCase();
if (roleText.contains('_')) {
return roleText
.split('_')
.map((word) =>
word.isNotEmpty ? word[0].toUpperCase() + word.substring(1) : '')
.join(' ');
}
return roleText.isNotEmpty
? roleText[0].toUpperCase() + roleText.substring(1)
: 'Petugas Desa';
}
String get nama {
// 1. Coba ambil dari AuthController displayName yang paling lengkap
final authDisplayName = _authController.displayName;
if (authDisplayName != null &&
authDisplayName != 'Pengguna' &&
authDisplayName != user?.email) {
return authDisplayName;
}
// 2. Coba ambil dari roleData jika merupakan PetugasDesaModel
final userData = _authController.userData;
if (userData != null && userData.roleData is PetugasDesaModel) {
final petugasData = userData.roleData as PetugasDesaModel;
if (petugasData.namaLengkap != null &&
petugasData.namaLengkap!.isNotEmpty) {
return petugasData.namaLengkap!;
}
}
// 3. Coba ambil dari user.name
if (user?.name != null && user!.name!.isNotEmpty) {
return user!.name!;
}
// 4. Fallback ke nama dari userProfile
if (userProfile['name'] != null &&
userProfile['name'].toString().isNotEmpty) {
return userProfile['name'];
}
// 5. Default fallback
return 'Petugas Desa';
}
// Getter untuk counter dari CounterService // Getter untuk counter dari CounterService
RxInt get jumlahNotifikasiBelumDibaca => RxInt get jumlahNotifikasiBelumDibaca =>
@ -64,6 +113,13 @@ class PetugasDesaController extends GetxController {
// Getter untuk nama desa dari profil pengguna // Getter untuk nama desa dari profil pengguna
String get desa { String get desa {
// Debug info
print('DEBUG: Memeriksa data desa...');
if (user != null) {
print('DEBUG: User ID: ${user!.id}, User email: ${user!.email}');
print('DEBUG: User desa: ${user!.desa}');
}
// Prioritaskan model desa dari user // Prioritaskan model desa dari user
if (user?.desa != null) { if (user?.desa != null) {
print('DEBUG: Menggunakan desa dari user model: ${user!.desa!.nama}'); print('DEBUG: Menggunakan desa dari user model: ${user!.desa!.nama}');
@ -77,9 +133,15 @@ class PetugasDesaController extends GetxController {
return desaNama; return desaNama;
} }
// Jika masih tidak ada, coba dari desaModel
if (desaModel.value != null) {
print('DEBUG: Menggunakan desa dari desaModel: ${desaModel.value!.nama}');
return desaModel.value!.nama;
}
// Fallback ke nilai default // Fallback ke nilai default
print('DEBUG: Menggunakan nilai default untuk desa'); print('DEBUG: Menggunakan nilai default untuk desa');
return userProfile['desa_id'] != null ? 'Desa' : 'Desa'; return 'Desa';
} }
@override @override
@ -109,34 +171,76 @@ class PetugasDesaController extends GetxController {
// Metode untuk memuat data profil pengguna dari cache // Metode untuk memuat data profil pengguna dari cache
Future<void> loadUserProfile() async { Future<void> loadUserProfile() async {
try { try {
// Jika user sudah ada di AuthController, tidak perlu mengambil data lagi // Jika user sudah ada di AuthController, gunakan data yang ada
if (user != null) { if (user != null) {
print('DEBUG: User ditemukan di AuthController: ${user!.email}'); print('DEBUG: User ditemukan di AuthController: ${user!.email}');
print('DEBUG: User desa: ${user!.desa?.nama}'); print('DEBUG: User desa: ${user!.desa?.nama}');
// Ambil data tambahan jika diperlukan, tapi gunakan cache // Tidak perlu mengambil data tambahan jika user.desa sudah ada
final profileData = await _supabaseService.getUserProfile(); if (user!.desa != null) {
if (profileData != null) { print(
print('DEBUG: Profile data ditemukan: ${profileData['name']}'); 'DEBUG: Menggunakan desa dari AuthController: ${user!.desa!.nama}');
userProfile.value = profileData; desaModel.value = user!.desa;
// Parse data desa jika ada // Perbarui userProfile untuk konsistensi
if (profileData['desa'] != null && if (userProfile.isEmpty) {
profileData['desa'] is Map<String, dynamic>) { userProfile.value = {
'name': user!.name ?? _authController.displayName,
'desa': user!.desa?.toJson(),
};
}
return; // Data sudah lengkap, tidak perlu fetch lagi
}
print(
'DEBUG: Data desa tidak ditemukan di AuthController, mencoba ambil dari cache');
// Jika tidak ada desa di AuthController, coba ambil dari userData roleData
final userData = _authController.userData;
if (userData != null) {
if (userData.roleData is PetugasDesaModel) {
final petugasData = userData.roleData as PetugasDesaModel;
if (petugasData.desa != null) {
print(
'DEBUG: Menggunakan desa dari roleData: ${petugasData.desa!.nama}');
desaModel.value = petugasData.desa;
// Perbarui userProfile untuk konsistensi
userProfile.value = {
'name': petugasData.displayName,
'desa': petugasData.desa?.toJson(),
};
return; // Data sudah lengkap, tidak perlu fetch lagi
}
}
}
// Jika tidak ada di cache, ambil dari API hanya jika benar-benar diperlukan
print('DEBUG: Data desa tidak ditemukan di cache, mengambil dari API');
final baseProfile = await _supabaseService.getUserProfile();
if (baseProfile != null) {
userProfile.value = baseProfile;
if (baseProfile['desa'] != null &&
baseProfile['desa'] is Map<String, dynamic>) {
try { try {
final desaData = profileData['desa'] as Map<String, dynamic>; final desaData = baseProfile['desa'] as Map<String, dynamic>;
print('DEBUG: Desa data ditemukan: $desaData'); print('DEBUG: Desa data ditemukan dari API: $desaData');
desaModel.value = DesaModel.fromJson(desaData);
} catch (e) { } catch (e) {
print('Error parsing desa data: $e'); print('Error parsing desa data: $e');
} }
} else { } else {
print('DEBUG: Desa data tidak ditemukan atau bukan Map'); print('DEBUG: Desa data tidak ditemukan di API');
} }
} else { } else {
print('DEBUG: Profile data tidak ditemukan'); print('DEBUG: Profile data tidak ditemukan dari API');
} }
} else { } else {
print('DEBUG: User tidak ditemukan di AuthController'); print(
'DEBUG: User tidak ditemukan di AuthController, mungkin belum login');
} }
} catch (e) { } catch (e) {
print('Error loading user profile: $e'); print('Error loading user profile: $e');

View File

@ -27,7 +27,7 @@ class PetugasDesaDashboardController extends GetxController {
// Controller untuk pencarian // Controller untuk pencarian
final TextEditingController searchController = TextEditingController(); final TextEditingController searchController = TextEditingController();
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
String get role => user?.role ?? 'PETUGASDESA'; String get role => user?.role ?? 'PETUGASDESA';
String get nama => user?.name ?? 'Petugas Desa'; String get nama => user?.name ?? 'Petugas Desa';

View File

@ -17,7 +17,7 @@ class RiwayatPengaduanController extends GetxController {
// Controller untuk pencarian // Controller untuk pencarian
final TextEditingController searchController = TextEditingController(); final TextEditingController searchController = TextEditingController();
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
@override @override
void onInit() { void onInit() {

View File

@ -36,7 +36,7 @@ class StokBantuanController extends GetxController {
// Tambahkan properti untuk waktu terakhir update // Tambahkan properti untuk waktu terakhir update
Rx<DateTime> lastUpdateTime = DateTime.now().obs; Rx<DateTime> lastUpdateTime = DateTime.now().obs;
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
@override @override
void onInit() { void onInit() {

View File

@ -198,7 +198,7 @@ class DetailDonaturView extends GetView<DonaturController> {
donatur.alamat ?? 'Tidak ada alamat'), donatur.alamat ?? 'Tidak ada alamat'),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildInfoItem(Icons.phone, 'Telepon', _buildInfoItem(Icons.phone, 'Telepon',
donatur.telepon ?? 'Tidak ada telepon'), donatur.noHp ?? 'Tidak ada telepon'),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildInfoItem( _buildInfoItem(
Icons.email, 'Email', donatur.email ?? 'Tidak ada email'), Icons.email, 'Email', donatur.email ?? 'Tidak ada email'),

View File

@ -1223,8 +1223,11 @@ class DetailPenyaluranPage extends StatelessWidget {
const Divider(height: 24), const Divider(height: 24),
if (warga != null) ...[ if (warga != null) ...[
_buildInfoRow('NIK', warga['nik'] ?? '-'), _buildInfoRow('NIK', warga['nik'] ?? '-'),
_buildInfoRow('Alamat Lengkap', _buildInfoRow('Alamat', warga['alamat'] ?? '-'),
'${warga['alamat'] ?? '-'} Desa ${warga['desa'] ?? '-'} Kecamatan ${warga['kecamatan'] ?? '-'} Kabupaten ${warga['kabupaten'] ?? '-'} Provinsi ${warga['provinsi'] ?? '-'}'), _buildInfoRow('Desa', warga['desa'] ?? '-'),
_buildInfoRow('Kecamatan', warga['kecamatan'] ?? '-'),
_buildInfoRow('Kabupaten', warga['kabupaten'] ?? '-'),
_buildInfoRow('Provinsi', warga['provinsi'] ?? '-'),
_buildInfoRow( _buildInfoRow(
'Jenis Kelamin', warga['jenis_kelamin'] ?? '-'), 'Jenis Kelamin', warga['jenis_kelamin'] ?? '-'),
_buildInfoRow('No. Telepon', warga['no_hp'] ?? '-'), _buildInfoRow('No. Telepon', warga['no_hp'] ?? '-'),

View File

@ -918,9 +918,8 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ),
if (selectedDonatur.value!.telepon != if (selectedDonatur.value!.noHp != null)
null) Text(selectedDonatur.value!.noHp!),
Text(selectedDonatur.value!.telepon!),
], ],
), ),
), ),
@ -984,9 +983,9 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
return ListTile( return ListTile(
title: title:
Text(donatur.nama ?? 'Tidak ada nama'), Text(donatur.nama ?? 'Tidak ada nama'),
subtitle: donatur.telepon != null subtitle: donatur.noHp != null
? Text(donatur.telepon!) ? Text(donatur.noHp!)
: null, : const Text('Tidak ada nomor telepon'),
dense: true, dense: true,
onTap: () { onTap: () {
selectedDonatur.value = donatur; selectedDonatur.value = donatur;
@ -1300,7 +1299,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
BuildContext context, Function(String) onDonaturAdded) { BuildContext context, Function(String) onDonaturAdded) {
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
final TextEditingController namaController = TextEditingController(); final TextEditingController namaController = TextEditingController();
final TextEditingController teleponController = TextEditingController(); final TextEditingController noHpController = TextEditingController();
final TextEditingController alamatController = TextEditingController(); final TextEditingController alamatController = TextEditingController();
final TextEditingController emailController = TextEditingController(); final TextEditingController emailController = TextEditingController();
final TextEditingController jenisController = TextEditingController(); final TextEditingController jenisController = TextEditingController();
@ -1352,24 +1351,24 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
// Telepon // Telepon
Text( Text(
'Nomor Telepon', 'Nomor HP',
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
TextFormField( TextFormField(
controller: teleponController, controller: noHpController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
hintText: 'Masukkan nomor telepon', hintText: 'Masukkan nomor HP',
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8), horizontal: 12, vertical: 8),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Nomor telepon harus diisi'; return 'Nomor HP harus diisi';
} }
return null; return null;
}, },
@ -1396,24 +1395,16 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
: jenisController.text, : jenisController.text,
items: const [ items: const [
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: 'Perorangan', value: 'Individu',
child: Text('Perorangan'), child: Text('Individu'),
), ),
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: 'Perusahaan', value: 'Perusahaan',
child: Text('Perusahaan'), child: Text('Perusahaan'),
), ),
DropdownMenuItem<String>( DropdownMenuItem<String>(
value: 'Lembaga', value: 'Organisasi',
child: Text('Lembaga'), child: Text('Organisasi'),
),
DropdownMenuItem<String>(
value: 'Komunitas',
child: Text('Komunitas'),
),
DropdownMenuItem<String>(
value: 'Lainnya',
child: Text('Lainnya'),
), ),
], ],
onChanged: (value) { onChanged: (value) {
@ -1478,7 +1469,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
final donaturId = await controller.tambahDonatur( final donaturId = await controller.tambahDonatur(
nama: namaController.text, nama: namaController.text,
telepon: teleponController.text, noHp: noHpController.text,
alamat: alamatController.text.isEmpty alamat: alamatController.text.isEmpty
? null ? null
: alamatController.text, : alamatController.text,

View File

@ -151,7 +151,6 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
return const DashboardView(); return const DashboardView();
case 1: case 1:
return const PenyaluranView(); return const PenyaluranView();
case 2: case 2:
return const PenitipanView(); return const PenitipanView();
case 3: case 3:
@ -182,22 +181,15 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
CircleAvatar( CircleAvatar(
radius: 30, radius: 30,
backgroundColor: Colors.white, backgroundColor: Colors.white,
backgroundImage: controller.user?.avatar != null && child: const Icon(
controller.user!.avatar!.isNotEmpty
? NetworkImage(controller.user!.avatar!)
: null,
child: controller.user?.avatar == null ||
controller.user!.avatar!.isEmpty
? const Icon(
Icons.person, Icons.person,
size: 40, size: 40,
color: AppTheme.primaryColor, color: AppTheme.primaryColor,
) ),
: null,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
controller.user?.name ?? 'Petugas Desa', controller.nama,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18, fontSize: 18,
@ -206,8 +198,8 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
), ),
Text( Text(
controller.user?.desa?.nama != null controller.user?.desa?.nama != null
? '${controller.user?.role} - ${controller.user!.desa!.nama}' ? '${controller.formattedRole} - ${controller.user!.desa!.nama}'
: controller.user?.role ?? 'PETUGAS_DESA', : controller.formattedRole,
style: TextStyle( style: TextStyle(
color: Colors.white.withAlpha(200), color: Colors.white.withAlpha(200),
fontSize: 14, fontSize: 14,
@ -251,9 +243,9 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
? Badge( ? Badge(
label: Text(controller.jumlahDiproses.value.toString()), label: Text(controller.jumlahDiproses.value.toString()),
backgroundColor: Colors.red, backgroundColor: Colors.red,
child: const Icon(Icons.support_outlined), child: const Icon(Icons.warning_amber_outlined),
) )
: const Icon(Icons.support_outlined), : const Icon(Icons.warning_amber_outlined),
title: const Text('Pengaduan'), title: const Text('Pengaduan'),
selected: controller.activeTabIndex.value == 3, selected: controller.activeTabIndex.value == 3,
selectedColor: AppTheme.primaryColor, selectedColor: AppTheme.primaryColor,
@ -272,6 +264,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
controller.changeTab(4); controller.changeTab(4);
}, },
), ),
const Divider(),
ListTile( ListTile(
leading: const Icon(Icons.person_add_outlined), leading: const Icon(Icons.person_add_outlined),
title: const Text('Kelola Penerima'), title: const Text('Kelola Penerima'),
@ -296,7 +289,6 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
Get.toNamed('/laporan-penyaluran'); Get.toNamed('/laporan-penyaluran');
}, },
), ),
const Divider(),
ListTile( ListTile(
leading: const Icon(Icons.person_outline), leading: const Icon(Icons.person_outline),
title: const Text('Profil'), title: const Text('Profil'),

View File

@ -310,11 +310,15 @@ class RiwayatPenitipanView extends GetView<PenitipanBantuanController> {
if (item.status == 'TERVERIFIKASI' && if (item.status == 'TERVERIFIKASI' &&
item.petugasDesaId != null) item.petugasDesaId != null)
Expanded( Expanded(
child: _buildItemDetail( child: GetBuilder<PenitipanBantuanController>(
id: 'petugas_data',
builder: (controller) => _buildItemDetail(
context, context,
icon: Icons.person, icon: Icons.person,
label: 'Diverifikasi Oleh', label: 'Diverifikasi Oleh',
value: controller.getPetugasDesaNama(item.petugasDesaId), value:
controller.getPetugasDesaNama(item.petugasDesaId),
),
), ),
), ),
], ],

View File

@ -1,14 +1,17 @@
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/data/models/user_model.dart'; import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/services/auth_service.dart'; import 'package:penyaluran_app/app/services/supabase_service.dart';
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
class ProfileController extends GetxController { class ProfileController extends GetxController {
final AuthService _authService = Get.find<AuthService>(); final SupabaseService _supabaseService = Get.find<SupabaseService>();
final AuthController _authController = Get.find<AuthController>();
final Rx<User?> user = Rx<User?>(null); final Rx<BaseUserModel?> user = Rx<BaseUserModel?>(null);
final RxBool isLoading = true.obs; final RxBool isLoading = true.obs;
final RxBool isEditing = false.obs; final RxBool isEditing = false.obs;
final Rx<Map<String, dynamic>?> roleData = Rx<Map<String, dynamic>?>(null);
// Form controllers // Form controllers
late TextEditingController nameController; late TextEditingController nameController;
@ -36,14 +39,23 @@ class ProfileController extends GetxController {
isLoading.value = true; isLoading.value = true;
try { try {
// Mendapatkan data user dari service // Mendapatkan data user dari service
final userData = await _authService.getCurrentUser(); final userData = await _supabaseService.getUserProfile();
user.value = userData; if (userData != null) {
user.value = BaseUserModel.fromJson(userData);
// Mengisi form controllers dengan data user // Mengisi form controllers dengan data user
if (userData != null) { nameController.text = user.value?.name ?? '';
nameController.text = userData.name ?? ''; emailController.text = user.value?.email ?? '';
emailController.text = userData.email ?? ''; // Catatan: BaseUserModel tidak memiliki properti phone
phoneController.text = userData.phone ?? ''; // Jika ada data role_data, simpan untuk ditampilkan
if (userData['role_data'] != null) {
roleData.value = userData['role_data'] as Map<String, dynamic>?;
// Jika role adalah warga, ambil no telepon dari role data
if (user.value?.role?.toLowerCase() == 'warga' &&
roleData.value?['no_hp'] != null) {
phoneController.text = roleData.value?['no_hp'] ?? '';
}
}
} }
} catch (e) { } catch (e) {
Get.snackbar( Get.snackbar(
@ -76,22 +88,45 @@ class ProfileController extends GetxController {
isLoading.value = true; isLoading.value = true;
try { try {
// Update user data final userData = user.value;
final updatedUser = User( if (userData == null) throw 'Data user tidak ditemukan';
id: user.value?.id,
name: nameController.text, // Update data sesuai role
switch (userData.role?.toLowerCase() ?? 'unknown') {
case 'warga':
await _supabaseService.updateWargaProfile(
userId: userData.id,
namaLengkap: nameController.text,
noHp: phoneController.text,
email: emailController.text, email: emailController.text,
phone: phoneController.text,
role: user.value?.role,
token: user.value?.token,
); );
break;
case 'donatur':
await _supabaseService.updateDonaturProfile(
userId: userData.id,
nama: nameController.text,
noHp: phoneController.text,
email: emailController.text,
);
break;
case 'petugas_desa':
await _supabaseService.updatePetugasDesaProfile(
userId: userData.id,
nama: nameController.text,
noHp: phoneController.text,
email: emailController.text,
);
break;
default:
throw 'Role tidak valid';
}
// Panggil API untuk update profil // Refresh data lokal
await _authService.updateProfile(updatedUser);
// Refresh data
await loadUserData(); await loadUserData();
// Refresh data di AuthController untuk menyebarkan perubahan ke seluruh aplikasi
await _authController.refreshUserData();
// Keluar dari mode edit // Keluar dari mode edit
isEditing.value = false; isEditing.value = false;
@ -131,7 +166,7 @@ class ProfileController extends GetxController {
isLoading.value = true; isLoading.value = true;
try { try {
// Panggil API untuk ganti password // Panggil API untuk ganti password
await _authService.changePassword(currentPassword, newPassword); await _supabaseService.changePassword(currentPassword, newPassword);
Get.back(); // Tutup dialog Get.back(); // Tutup dialog

View File

@ -87,6 +87,7 @@ class ProfileView extends GetView<ProfileController> {
Widget _buildProfileForm() { Widget _buildProfileForm() {
return Obx(() { return Obx(() {
final isEditing = controller.isEditing.value; final isEditing = controller.isEditing.value;
final user = controller.user.value;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -119,7 +120,7 @@ class ProfileView extends GetView<ProfileController> {
labelText: 'Email', labelText: 'Email',
border: OutlineInputBorder(), border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email), prefixIcon: Icon(Icons.email),
enabled: isEditing, enabled: false, // Email tidak bisa diubah
), ),
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
), ),
@ -136,6 +137,113 @@ class ProfileView extends GetView<ProfileController> {
), ),
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
), ),
const SizedBox(height: 16),
// Informasi tambahan sesuai role
if (user != null) ...[
if (user.role?.toLowerCase() == 'warga') ...[
const SizedBox(height: 24),
const Text(
'Informasi Warga',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Obx(() {
final roleData = controller.roleData.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(
Icons.perm_identity, 'NIK', roleData?['nik'] ?? '-'),
const SizedBox(height: 8),
_buildInfoRow(Icons.wc, 'Jenis Kelamin',
roleData?['jenis_kelamin'] ?? '-'),
const SizedBox(height: 8),
_buildInfoRow(
Icons.home, 'Alamat', roleData?['alamat'] ?? '-'),
if (user.desa != null) ...[
const SizedBox(height: 8),
_buildInfoRow(
Icons.location_city, 'Desa', user.desa!.nama),
const SizedBox(height: 8),
_buildInfoRow(Icons.location_on, 'Kecamatan',
user.desa!.kecamatan ?? ''),
const SizedBox(height: 8),
_buildInfoRow(Icons.location_on, 'Kabupaten',
user.desa!.kabupaten ?? ''),
const SizedBox(height: 8),
_buildInfoRow(Icons.location_on, 'Provinsi',
user.desa!.provinsi ?? ''),
],
],
);
}),
],
if (user.role?.toLowerCase() == 'donatur') ...[
const SizedBox(height: 24),
const Text(
'Informasi Donatur',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Obx(() {
final roleData = controller.roleData.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.business, 'Instansi',
roleData?['instansi'] ?? '-'),
const SizedBox(height: 8),
_buildInfoRow(
Icons.work, 'Jabatan', roleData?['jabatan'] ?? '-'),
],
);
}),
],
if (user.role?.toLowerCase() == 'petugas_desa') ...[
const SizedBox(height: 24),
const Text(
'Informasi Petugas Desa',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Obx(() {
final roleData = controller.roleData.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.badge, 'NIP', roleData?['nip'] ?? '-'),
const SizedBox(height: 8),
_buildInfoRow(
Icons.work, 'Jabatan', roleData?['jabatan'] ?? '-'),
if (user.desa != null) ...[
const SizedBox(height: 8),
_buildInfoRow(
Icons.location_city, 'Desa', user.desa!.nama),
const SizedBox(height: 8),
_buildInfoRow(Icons.location_on, 'Kecamatan',
user.desa!.kecamatan ?? ''),
const SizedBox(height: 8),
_buildInfoRow(Icons.location_on, 'Kabupaten',
user.desa!.kabupaten ?? ''),
const SizedBox(height: 8),
_buildInfoRow(Icons.location_on, 'Provinsi',
user.desa!.provinsi ?? ''),
],
],
);
}),
],
],
], ],
); );
}); });
@ -230,4 +338,23 @@ class ProfileView extends GetView<ProfileController> {
), ),
); );
} }
Widget _buildInfoRow(IconData icon, String label, String value) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 18, color: Colors.grey[700]),
const SizedBox(width: 8),
Expanded(
child: Text(
'$label: $value',
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
),
],
);
}
} }

View File

@ -11,7 +11,7 @@ class WargaDashboardController extends GetxController {
final AuthController _authController = Get.find<AuthController>(); final AuthController _authController = Get.find<AuthController>();
final SupabaseService _supabaseService = SupabaseService.to; final SupabaseService _supabaseService = SupabaseService.to;
final Rx<UserModel?> currentUser = Rx<UserModel?>(null); final Rx<BaseUserModel?> currentUser = Rx<BaseUserModel?>(null);
// Indeks tab yang aktif di bottom navigation bar // Indeks tab yang aktif di bottom navigation bar
final RxInt activeTabIndex = 0.obs; final RxInt activeTabIndex = 0.obs;
@ -41,9 +41,18 @@ class WargaDashboardController extends GetxController {
final RxInt jumlahNotifikasiBelumDibaca = 0.obs; final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
// Getter untuk data user // Getter untuk data user
UserModel? get user => _authController.user; BaseUserModel? get user => _authController.baseUser;
String get role => user?.role ?? 'WARGA'; String get role => user?.role ?? 'WARGA';
String get nama => user?.name ?? 'Warga'; String get nama {
// Gunakan namaLengkap dari roleData jika tersedia
if (_authController.isWarga && _authController.roleData != null) {
return _authController.roleData.namaLengkap ??
_authController.displayName;
}
// Gunakan displayName dari AuthController
return _authController.displayName;
}
String? get desa => user?.desa?.nama; String? get desa => user?.desa?.nama;
@override @override
@ -54,7 +63,30 @@ class WargaDashboardController extends GetxController {
} }
void loadUserData() { void loadUserData() {
currentUser.value = _authController.user; currentUser.value = _authController.baseUser;
// Tambahkan 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) {
print(
'DEBUG WARGA: userData ada, role: ${_authController.userData!.baseUser.roleName}');
if (_authController.isWarga) {
print('DEBUG WARGA: User adalah warga');
var wargaData = _authController.roleData;
print('DEBUG WARGA: Data warga: ${wargaData?.namaLengkap}');
} else {
print('DEBUG WARGA: User bukan warga');
}
} else {
print('DEBUG WARGA: userData null');
}
} }
void fetchData() async { void fetchData() async {
@ -90,14 +122,8 @@ 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();
// Pertama, cari warga_id berdasarkan user_id // Gunakan langsung ID pengguna sebagai warga_id
final wargaResponse = await _supabaseService.client final wargaId = user!.id;
.from('warga')
.select('id')
.eq('user_id', user!.id)
.single();
final wargaId = wargaResponse['id'];
// 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 =
@ -221,14 +247,8 @@ class WargaDashboardController extends GetxController {
// Fungsi untuk mengambil data pengajuan kelayakan // Fungsi untuk mengambil data pengajuan kelayakan
Future<void> fetchPengajuanKelayakan() async { Future<void> fetchPengajuanKelayakan() async {
try { try {
// Pertama, cari warga_id berdasarkan user_id // Gunakan langsung ID pengguna sebagai warga_id
final wargaResponse = await _supabaseService.client final wargaId = user!.id;
.from('warga')
.select('id')
.eq('user_id', user!.id)
.single();
final wargaId = wargaResponse['id'];
final response = await _supabaseService.client final response = await _supabaseService.client
.from('xx02_pengajuan_kelayakan_bantuan') .from('xx02_pengajuan_kelayakan_bantuan')
@ -257,19 +277,17 @@ class WargaDashboardController extends GetxController {
totalPengajuanDitolak.value = totalPengajuanDitolak.value =
pengajuan.where((p) => p.status == StatusKelayakan.DITOLAK).length; pengajuan.where((p) => p.status == StatusKelayakan.DITOLAK).length;
} catch (e) { } catch (e) {
print('Error fetching pengajuan kelayakan: $e'); print('Error fetchPengajuanKelayakan: $e');
} }
} }
// Fungsi untuk mengambil data pengaduan // Fungsi untuk mengambil data pengaduan
Future<void> fetchPengaduan() async { Future<void> fetchPengaduan() async {
try { try {
final wargaData = await _supabaseService.getWargaByUserId(); // Gunakan langsung ID user dari AuthController, bukan getWargaByUserId()
if (wargaData == null) { String wargaId = user!.id;
return; print('DEBUG WARGA: Mengambil pengaduan untuk warga ID: $wargaId');
}
final String wargaId = wargaData['id'];
final response = await _supabaseService final response = await _supabaseService
.getPengaduanWargaWithPenerimaPenyaluran(wargaId); .getPengaduanWargaWithPenerimaPenyaluran(wargaId);
@ -287,15 +305,21 @@ class WargaDashboardController extends GetxController {
.length; .length;
totalPengaduanSelesai.value = totalPengaduanSelesai.value =
pengaduanList.where((p) => p.status == 'SELESAI').length; pengaduanList.where((p) => p.status == 'SELESAI').length;
print(
'DEBUG WARGA: Berhasil mendapatkan ${pengaduanList.length} pengaduan');
} else {
print('DEBUG WARGA: Tidak ada pengaduan yang ditemukan');
} }
} catch (e) { } catch (e) {
print('Error fetching pengaduan: $e'); print('DEBUG WARGA: Error fetching pengaduan: $e');
} }
} }
// Fungsi untuk mengambil data notifikasi // Fungsi untuk mengambil data notifikasi
Future<void> fetchNotifikasi() async { Future<void> fetchNotifikasi() async {
try { try {
// Notifikasi masih menggunakan user_id karena tabelnya terpisah
final response = await _supabaseService.client final response = await _supabaseService.client
.from('notifikasi') .from('notifikasi')
.select('*') .select('*')
@ -429,13 +453,8 @@ class WargaDashboardController extends GetxController {
try { try {
isLoading.value = true; isLoading.value = true;
// Cari warga_id berdasarkan user_id // Gunakan langsung ID pengguna sebagai warga_id
final wargaData = await _supabaseService.getWargaByUserId(); final String wargaId = user!.id;
if (wargaData == null) {
throw Exception('Data warga tidak ditemukan');
}
final String wargaId = wargaData['id'];
// Upload foto pengaduan jika ada // Upload foto pengaduan jika ada
List<String> fotoPengaduanUrls = []; List<String> fotoPengaduanUrls = [];

View File

@ -7,15 +7,17 @@ class AuthService extends GetxService {
final Dio _dio = Dio(); final Dio _dio = Dio();
final FlutterSecureStorage _storage = const FlutterSecureStorage(); final FlutterSecureStorage _storage = const FlutterSecureStorage();
final Rx<User?> currentUser = Rx<User?>(null); final Rx<BaseUserModel?> currentUser = Rx<BaseUserModel?>(null);
String? _token;
// Mendapatkan data user saat ini // Mendapatkan data user saat ini
Future<User?> getCurrentUser() async { Future<BaseUserModel?> getCurrentUser() async {
try { try {
// Implementasi untuk mendapatkan data user dari API atau local storage // Implementasi untuk mendapatkan data user dari API atau local storage
// Contoh implementasi sederhana: // Contoh implementasi sederhana:
final token = await _storage.read(key: 'token'); final token = await _storage.read(key: 'token');
if (token == null) return null; if (token == null) return null;
_token = token;
final response = await _dio.get( final response = await _dio.get(
'/api/user/profile', '/api/user/profile',
@ -27,7 +29,7 @@ class AuthService extends GetxService {
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
final user = User.fromJson(response.data['data']); final user = BaseUserModel.fromJson(response.data['data']);
currentUser.value = user; currentUser.value = user;
return user; return user;
} }
@ -40,7 +42,7 @@ class AuthService extends GetxService {
} }
// Update profil user // Update profil user
Future<bool> updateProfile(User user) async { Future<bool> updateProfile(BaseUserModel user) async {
try { try {
final token = await _storage.read(key: 'token'); final token = await _storage.read(key: 'token');
if (token == null) return false; if (token == null) return false;
@ -50,7 +52,7 @@ class AuthService extends GetxService {
data: { data: {
'name': user.name, 'name': user.name,
'email': user.email, 'email': user.email,
'phone': user.phone, // 'phone' tidak tersedia di BaseUserModel, jadi kita hilangkan atau gunakan parameter lain
}, },
options: Options( options: Options(
headers: { headers: {
@ -100,7 +102,7 @@ class AuthService extends GetxService {
} }
// Login // Login
Future<User?> login(String email, String password) async { Future<BaseUserModel?> login(String email, String password) async {
try { try {
final response = await _dio.post( final response = await _dio.post(
'/api/auth/login', '/api/auth/login',
@ -111,11 +113,13 @@ class AuthService extends GetxService {
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
final user = User.fromJson(response.data['data']); final user = BaseUserModel.fromJson(response.data['data']);
// Simpan token // Simpan token yang datang dari respons
if (user.token != null) { if (response.data['token'] != null) {
await _storage.write(key: 'token', value: user.token); final token = response.data['token'].toString();
await _storage.write(key: 'token', value: token);
_token = token;
} }
currentUser.value = user; currentUser.value = user;
@ -148,6 +152,7 @@ class AuthService extends GetxService {
} finally { } finally {
// Hapus token dan user data // Hapus token dan user data
await _storage.delete(key: 'token'); await _storage.delete(key: 'token');
_token = null;
currentUser.value = null; currentUser.value = null;
// Navigasi ke halaman login // Navigasi ke halaman login
@ -155,6 +160,12 @@ class AuthService extends GetxService {
} }
} }
// Mendapatkan token autentikasi
Future<String?> getToken() async {
if (_token != null) return _token;
return await _storage.read(key: 'token');
}
// Inisialisasi service // Inisialisasi service
Future<AuthService> init() async { Future<AuthService> init() async {
// Coba mendapatkan user saat ini // Coba mendapatkan user saat ini

View File

@ -1,4 +1,8 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:penyaluran_app/app/data/models/donatur_model.dart';
import 'package:penyaluran_app/app/data/models/petugas_desa_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/warga_model.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import 'dart:io'; import 'dart:io';
@ -73,21 +77,6 @@ class SupabaseService extends GetxService {
); );
} }
// Metode untuk login
Future<AuthResponse> signIn(String email, String password) async {
final response = await client.auth.signInWithPassword(
email: email,
password: password,
);
if (response.user != null) {
_isSessionInitialized = true;
print('DEBUG: Login berhasil, sesi diinisialisasi');
}
return response;
}
// Metode untuk logout // Metode untuk logout
Future<void> signOut() async { Future<void> signOut() async {
_cachedUserProfile = null; // Hapus cache saat logout _cachedUserProfile = null; // Hapus cache saat logout
@ -122,45 +111,301 @@ class SupabaseService extends GetxService {
return false; return false;
} }
// Metode untuk mendapatkan profil pengguna // Metode untuk mendapatkan profil pengguna dasar
Future<Map<String, dynamic>?> getUserProfile() async { Future<BaseUserModel?> getBaseUserProfile() async {
final user = currentUser; final user = currentUser;
if (user == null) return null; if (user == null) return null;
try { try {
// Gunakan cache jika tersedia // Gunakan auth.getUser() daripada mengakses tabel auth.users
if (_cachedUserProfile != null && _cachedUserProfile!['id'] == user.id) { final userData = await client.auth.getUser();
print('Menggunakan data profil dari cache');
print('userData: ${userData.user}');
if (userData.user == null) {
print('Tidak ada data user ditemukan');
return null;
}
// Dapatkan role dari tabel khusus roles jika diperlukan
String roleName = 'warga'; // default
if (userData.user!.userMetadata?['role_id'] != null) {
try {
final roleResponse = await client
.from('roles')
.select('role_name')
.eq('id', userData.user!.userMetadata!['role_id'])
.maybeSingle();
print('roleResponse: $roleResponse');
if (roleResponse != null) {
roleName = roleResponse['role_name'];
}
} catch (e) {
print('Error saat mengambil role: $e');
// Lanjutkan dengan role default jika gagal
}
}
// Gabungkan data user dan role
Map<String, dynamic> combinedData = {
'id': userData.user!.id,
'email': userData.user!.email,
'created_at': userData.user!.createdAt,
'updated_at': userData.user!.updatedAt,
'role_id': userData.user!.userMetadata?['role_id'],
'roles': {'role_name': roleName}
};
return BaseUserModel.fromJson(combinedData);
} catch (e) {
print('Error pada getBaseUserProfile: $e');
return null;
}
}
// Metode untuk mendapatkan profil pengguna dengan data lengkap
Future<Map<String, dynamic>?> getUserProfile() async {
try {
// Jika cache profil tersedia, gunakan
if (_cachedUserProfile != null) {
return _cachedUserProfile; return _cachedUserProfile;
} }
final response = await client // Jika tidak ada cache, ambil dari database
.from('user_profile') final user = currentUser;
.select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)') if (user == null) {
.eq('id', user.id) print('DEBUG: Tidak ada user yang login');
.maybeSingle(); return null;
print('response: $response');
// Simpan ke cache
_cachedUserProfile = response;
// Log untuk debugging
if (response != null && response['desa'] != null) {
print('Desa data: ${response['desa']}');
print('Desa type: ${response['desa'].runtimeType}');
} }
return response; final userId = user.id;
// Debug info
print('DEBUG: Mengambil data user profile untuk ID: $userId');
// Ambil data role dari database
final roleResponse = await client
.from('users_with_roles')
.select('role_name')
.eq('id', userId)
.maybeSingle();
if (roleResponse == null) {
print('DEBUG: Tidak menemukan role untuk user ID: $userId');
return null;
}
final roleName = roleResponse['role_name'];
print('DEBUG: Role name: $roleName');
// Ambil data khusus untuk role tersebut
Map<String, dynamic>? roleData;
switch (roleName.toLowerCase()) {
case 'warga':
final wargaResponse = await client
.from('warga')
.select(
'*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId)
.maybeSingle();
roleData = wargaResponse;
break;
case 'petugas_desa':
final petugasResponse = await client
.from('petugas_desa')
.select(
'*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId)
.maybeSingle();
roleData = petugasResponse;
break;
case 'donatur':
final donaturResponse = await client
.from('donatur')
.select('*')
.eq('id', userId)
.maybeSingle();
roleData = donaturResponse;
break;
default:
print('DEBUG: Role tidak dikenali: $roleName');
return null;
}
if (roleData == null) {
print('DEBUG: Tidak menemukan data untuk role: $roleName');
return null;
}
// Siapkan data kombinasi dari Supabase Auth + data dari tabel role
final combinedData = {
'id': userId,
'email': user.email,
'role': roleName,
'created_at': user.createdAt,
'updated_at': user.updatedAt,
'role_data': roleData,
};
// Tambahkan nama dari data role jika ada berdasarkan role
switch (roleName.toLowerCase()) {
case 'warga':
if (roleData['nama_lengkap'] != null) {
combinedData['name'] = roleData['nama_lengkap'];
}
break;
case 'petugas_desa':
case 'donatur':
if (roleData['nama'] != null) {
combinedData['name'] = roleData['nama'];
}
break;
}
// Tambahkan data role-specific
if (roleData != null) {
combinedData['role_data'] = roleData;
// Tambahkan data desa jika ada
if (roleData['desa'] != null) {
combinedData['desa'] = roleData['desa'];
}
// Tambahkan nama dari data role jika ada
if (roleData['nama_lengkap'] != null) {
combinedData['name'] = roleData['nama_lengkap'];
}
}
// Cache profil untuk penggunaan berikutnya
_cachedUserProfile = combinedData;
print('combinedData: $combinedData');
return combinedData;
} catch (e) { } catch (e) {
print('Error pada getUserProfile: $e'); print('Error pada getUserProfile: $e');
return null; return null;
} }
} }
// Metode untuk mendapatkan role pengguna // Metode eksplisit untuk membersihkan cache profil
Future<String?> getUserRole() async { void clearUserProfileCache() {
final profile = await getUserProfile(); print('DEBUG: Membersihkan cache profil pengguna');
return profile?['role']; _cachedUserProfile = null;
}
// Metode untuk mendapatkan data warga
Future<WargaModel?> getWargaProfile(String userId) async {
try {
final response = await client
.from('warga')
.select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId) // id di tabel warga = userId
.maybeSingle();
if (response == null) {
print('Data warga tidak ditemukan');
return null;
}
return WargaModel.fromJson(response);
} catch (e) {
print('Error pada getWargaProfile: $e');
return null;
}
}
// Metode untuk mendapatkan data donatur
Future<DonaturModel?> getDonaturProfile(String userId) async {
try {
final response = await client
.from('donatur')
.select('*')
.eq('id', userId) // id di tabel donatur = userId
.maybeSingle();
if (response == null) {
print('Data donatur tidak ditemukan');
return null;
}
return DonaturModel.fromJson(response);
} catch (e) {
print('Error pada getDonaturProfile: $e');
return null;
}
}
// Metode untuk mendapatkan data petugas desa
Future<PetugasDesaModel?> getPetugasDesaProfile(String userId) async {
try {
final response = await client
.from('petugas_desa')
.select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', userId) // id di tabel petugas_desa = userId
.maybeSingle();
if (response == null) {
print('Data petugas desa tidak ditemukan');
return null;
}
return PetugasDesaModel.fromJson(response);
} catch (e) {
print('Error pada getPetugasDesaProfile: $e');
return null;
}
}
// Metode untuk mendapatkan data user lengkap berdasarkan role
// @deprecated Gunakan AuthProvider.getCurrentUser() sebagai gantinya
// Metode ini dipertahankan untuk kompatibilitas mundur
Future<UserData?> getUserData() async {
print(
'WARNING: Menggunakan metode getUserData() yang sudah deprecated. Gunakan AuthProvider.getCurrentUser() sebagai gantinya.');
final baseUser = await getBaseUserProfile();
if (baseUser == null) return null;
try {
switch (baseUser.roleName.toLowerCase()) {
case 'warga':
final wargaData = await getWargaProfile(baseUser.id);
if (wargaData != null) {
return UserData<WargaModel>(
baseUser: baseUser,
roleData: wargaData,
);
}
break;
case 'donatur':
final donaturData = await getDonaturProfile(baseUser.id);
if (donaturData != null) {
return UserData<DonaturModel>(
baseUser: baseUser,
roleData: donaturData,
);
}
break;
case 'petugas_desa':
final petugasDesaData = await getPetugasDesaProfile(baseUser.id);
if (petugasDesaData != null) {
return UserData<PetugasDesaModel>(
baseUser: baseUser,
roleData: petugasDesaData,
);
}
break;
}
// Jika data role-specific tidak ditemukan
print('Data spesifik tidak ditemukan untuk role: ${baseUser.roleName}');
return null;
} catch (e) {
print('Error pada getUserData: $e');
return null;
}
} }
// ==================== PETUGAS DESA METHODS ==================== // ==================== PETUGAS DESA METHODS ====================
@ -211,6 +456,7 @@ class SupabaseService extends GetxService {
Future<List<Map<String, dynamic>>?> getNotifikasiBelumDibaca( Future<List<Map<String, dynamic>>?> getNotifikasiBelumDibaca(
String userId) async { String userId) async {
try { try {
// Notifikasi masih menggunakan user_id karena tabelnya terpisah
final response = await client final response = await client
.from('notifikasi') .from('notifikasi')
.select('*') .select('*')
@ -457,7 +703,7 @@ class SupabaseService extends GetxService {
try { try {
final response = await client final response = await client
.from('penitipan_bantuan') .from('penitipan_bantuan')
.select('*, donatur:donatur_id(*), stok_bantuan:stok_bantuan_id(*)') .select('*, donatur(*), stok_bantuan:stok_bantuan_id(*)')
.order('tanggal_penitipan', ascending: false); .order('tanggal_penitipan', ascending: false);
return response; return response;
@ -472,7 +718,7 @@ class SupabaseService extends GetxService {
try { try {
final response = await client final response = await client
.from('penitipan_bantuan') .from('penitipan_bantuan')
.select('*, donatur:donatur_id(*), stok_bantuan:stok_bantuan_id(*)') .select('*, donatur(*), stok_bantuan:stok_bantuan_id(*)')
.eq('status', 'TERVERIFIKASI') .eq('status', 'TERVERIFIKASI')
.order('tanggal_penitipan', ascending: false); .order('tanggal_penitipan', ascending: false);
@ -677,6 +923,12 @@ class SupabaseService extends GetxService {
// Metode untuk menambahkan donatur baru // Metode untuk menambahkan donatur baru
Future<String?> tambahDonatur(Map<String, dynamic> donaturData) async { Future<String?> tambahDonatur(Map<String, dynamic> donaturData) async {
try { try {
// Pastikan field nama_lengkap ada di donaturData
if (donaturData.containsKey('nama')) {
donaturData['nama_lengkap'] = donaturData['nama'];
donaturData.remove('nama');
}
final response = final response =
await client.from('donatur').insert(donaturData).select('id'); await client.from('donatur').insert(donaturData).select('id');
if (response.isNotEmpty) { if (response.isNotEmpty) {
@ -798,8 +1050,8 @@ class SupabaseService extends GetxService {
.from('tindakan_pengaduan') .from('tindakan_pengaduan')
.select(''' .select('''
*, *,
petugas:petugas_id(id, nama, email), petugas:petugas_id(id, nama_lengkap, nip),
verifikator:verifikator_id(id, nama, email) 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);
@ -956,7 +1208,7 @@ class SupabaseService extends GetxService {
final response = await client final response = await client
.from('warga') .from('warga')
.select('*') .select('*')
.eq('user_id', user.id) .eq('id', user.id)
.maybeSingle(); .maybeSingle();
return response; return response;
@ -981,8 +1233,27 @@ class SupabaseService extends GetxService {
final user = currentUser; final user = currentUser;
if (user == null) throw 'User tidak ditemukan'; if (user == null) throw 'User tidak ditemukan';
// Dapatkan role_id untuk warga
final roleResponse = await client
.from('roles')
.select('id')
.eq('role_name', 'warga')
.single();
if (roleResponse == null) {
throw 'Role warga tidak ditemukan';
}
final roleId = roleResponse['id'];
// Update role_id di auth.users
await client
.from('auth.users')
.update({'role_id': roleId}).eq('id', user.id);
// Buat profil warga
await client.from('warga').insert({ await client.from('warga').insert({
'user_id': user.id, 'id': user.id, // Gunakan id dari auth.users sebagai id di tabel warga
'nik': nik, 'nik': nik,
'nama_lengkap': namaLengkap, 'nama_lengkap': namaLengkap,
'jenis_kelamin': jenisKelamin, 'jenis_kelamin': jenisKelamin,
@ -995,15 +1266,111 @@ class SupabaseService extends GetxService {
'created_at': DateTime.now().toIso8601String(), 'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(),
}); });
} catch (e) {
print('Error creating warga profile: $e');
throw e.toString();
}
}
// Update user profile role // Metode untuk membuat profil donatur
await client.from('user_profile').upsert({ Future<void> createDonaturProfile({
'id': user.id, required String nama_lengkap,
'role': 'WARGA', String? alamat,
String? noHp,
String? email,
String? jenis,
String? deskripsi,
}) async {
try {
final user = currentUser;
if (user == null) throw 'User tidak ditemukan';
// Dapatkan role_id untuk donatur
final roleResponse = await client
.from('roles')
.select('id')
.eq('role_name', 'donatur')
.single();
if (roleResponse == null) {
throw 'Role donatur tidak ditemukan';
}
final roleId = roleResponse['id'];
// Update role_id di auth.users
await client
.from('auth.users')
.update({'role_id': roleId}).eq('id', user.id);
// Buat profil donatur
await client.from('donatur').insert({
'id': user.id, // Gunakan id dari auth.users sebagai id di tabel donatur
'nama_lengkap': nama_lengkap,
'alamat': alamat,
'no_hp': noHp,
'email': email,
'jenis': jenis,
'deskripsi': deskripsi,
'status': 'AKTIF',
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(),
}); });
} catch (e) { } catch (e) {
print('Error creating warga profile: $e'); print('Error creating donatur profile: $e');
throw e.toString();
}
}
// Metode untuk membuat profil petugas desa
Future<void> createPetugasDesaProfile({
required String nama_lengkap,
String? alamat,
String? noHp,
String? email,
String? jabatan,
String? nip,
required String desa_id,
}) async {
try {
final user = currentUser;
if (user == null) throw 'User tidak ditemukan';
// Dapatkan role_id untuk petugas desa
final roleResponse = await client
.from('roles')
.select('id')
.eq('role_name', 'petugas_desa')
.single();
if (roleResponse == null) {
throw 'Role petugas desa tidak ditemukan';
}
final roleId = roleResponse['id'];
// Update role_id di auth.users
await client
.from('auth.users')
.update({'role_id': roleId}).eq('id', user.id);
// Buat profil petugas desa
await client.from('petugas_desa').insert({
'id': user
.id, // Gunakan id dari auth.users sebagai id di tabel petugas_desa
'nama_lengkap': nama_lengkap,
'alamat': alamat,
'no_hp': noHp,
'email': email,
'jabatan': jabatan,
'nip': nip,
'desa_id': desa_id,
'status': 'AKTIF',
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
});
} catch (e) {
print('Error creating petugas desa profile: $e');
throw e.toString(); throw e.toString();
} }
} }
@ -1015,6 +1382,7 @@ class SupabaseService extends GetxService {
final user = currentUser; final user = currentUser;
if (user == null) return []; if (user == null) return [];
// Notifikasi masih menggunakan user_id karena tabelnya terpisah
final query = unreadOnly final query = unreadOnly
? client ? client
.from('notifikasi') .from('notifikasi')
@ -1054,19 +1422,17 @@ class SupabaseService extends GetxService {
try { try {
print('Mengambil data petugas desa dengan ID: $petugasDesaId'); print('Mengambil data petugas desa dengan ID: $petugasDesaId');
// Coba ambil dari tabel user_profile dulu // Gunakan tabel petugas_desa sebagai pengganti user_profile
final response = await client final response = await client
.from('user_profile') .from('petugas_desa')
.select('*') .select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
.eq('id', petugasDesaId) .eq('id', petugasDesaId)
.eq('role', 'PETUGASDESA')
.maybeSingle(); .maybeSingle();
print('Response: $response'); print('Response: $response');
if (response != null) { if (response != null) {
print( print('Berhasil mendapatkan data petugas desa: $response');
'Berhasil mendapatkan data petugas desa dari user_profile: $response');
return response; return response;
} }
@ -1245,4 +1611,126 @@ class SupabaseService extends GetxService {
return null; return null;
} }
} }
// Metode untuk update profil warga
Future<void> updateWargaProfile({
required String userId,
required String namaLengkap,
String? noHp,
String? email,
String? alamat,
String? nik,
String? tempatLahir,
DateTime? tanggalLahir,
String? jenisKelamin,
String? agama,
String? kategoriEkonomi,
}) async {
try {
final data = {
'nama_lengkap': namaLengkap,
'updated_at': DateTime.now().toIso8601String(),
};
if (noHp != null) data['no_hp'] = noHp;
if (email != null) data['email'] = email;
if (alamat != null) data['alamat'] = alamat;
if (nik != null) data['nik'] = nik;
if (tempatLahir != null) data['tempat_lahir'] = tempatLahir;
if (tanggalLahir != null)
data['tanggal_lahir'] = tanggalLahir.toIso8601String();
if (jenisKelamin != null) data['jenis_kelamin'] = jenisKelamin;
if (agama != null) data['agama'] = agama;
if (kategoriEkonomi != null) data['kategori_ekonomi'] = kategoriEkonomi;
await client.from('warga').update(data).eq('id', userId);
// Hapus cache setelah update
clearUserProfileCache();
} catch (e) {
print('Error updating warga profile: $e');
throw e.toString();
}
}
// Metode untuk update profil donatur
Future<void> updateDonaturProfile({
required String userId,
required String nama,
String? alamat,
String? noHp,
String? email,
String? jenis,
String? instansi,
String? jabatan,
}) async {
try {
final data = {
'nama': nama,
'updated_at': DateTime.now().toIso8601String(),
};
if (alamat != null) data['alamat'] = alamat;
if (noHp != null) data['no_hp'] = noHp;
if (email != null) data['email'] = email;
if (jenis != null) data['jenis'] = jenis;
if (instansi != null) data['instansi'] = instansi;
if (jabatan != null) data['jabatan'] = jabatan;
await client.from('donatur').update(data).eq('id', userId);
// Hapus cache setelah update
clearUserProfileCache();
} catch (e) {
print('Error updating donatur profile: $e');
throw e.toString();
}
}
// Metode untuk update profil petugas desa
Future<void> updatePetugasDesaProfile({
required String userId,
required String nama,
String? alamat,
String? noHp,
String? email,
String? nip,
String? jabatan,
}) async {
try {
final data = {
'nama': nama,
'updated_at': DateTime.now().toIso8601String(),
};
if (alamat != null) data['alamat'] = alamat;
if (noHp != null) data['no_hp'] = noHp;
if (email != null) data['email'] = email;
if (nip != null) data['nip'] = nip;
if (jabatan != null) data['jabatan'] = jabatan;
await client.from('petugas_desa').update(data).eq('id', userId);
// Hapus cache setelah update
clearUserProfileCache();
} catch (e) {
print('Error updating petugas desa profile: $e');
throw e.toString();
}
}
// Metode untuk ganti password
Future<void> changePassword(
String currentPassword, String newPassword) async {
try {
await client.auth.updateUser(
UserAttributes(
password: newPassword,
),
);
} catch (e) {
print('Error changing password: $e');
throw e.toString();
}
}
} }