Tambahkan fungsionalitas pendaftaran donatur baru tanpa konfirmasi email di AuthProvider. Perbarui model DonaturModel untuk menyertakan properti isManual. Modifikasi tampilan dan controller untuk mendukung registrasi donatur, termasuk validasi form dan navigasi ke halaman pendaftaran. Perbarui rute aplikasi untuk menambahkan halaman pendaftaran donatur. Selain itu, perbarui beberapa file konfigurasi dan dependensi untuk mendukung perubahan ini.
This commit is contained in:
@ -439,29 +439,6 @@ class JadwalPenyaluranController extends GetxController {
|
||||
.insert(penerimaPenyaluran);
|
||||
}
|
||||
|
||||
// Update stok bantuan (kurangi dengan total stok yang dibutuhkan)
|
||||
try {
|
||||
// Dapatkan stok saat ini
|
||||
final stokData = await _supabaseService.client
|
||||
.from('stok_bantuan')
|
||||
.select('total_stok')
|
||||
.eq('id', stokBantuanId)
|
||||
.single();
|
||||
|
||||
if (stokData['total_stok'] != null) {
|
||||
final currentStok = stokData['total_stok'].toDouble();
|
||||
final newStok = currentStok - totalStokDibutuhkan;
|
||||
|
||||
// Update stok bantuan dengan nilai baru
|
||||
await _supabaseService.client
|
||||
.from('stok_bantuan')
|
||||
.update({'total_stok': newStok}).eq('id', stokBantuanId);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error updating stok bantuan: $e');
|
||||
// Tidak throw exception di sini karena penyaluran sudah disimpan
|
||||
}
|
||||
|
||||
// Setelah berhasil menambahkan, refresh data
|
||||
await loadJadwalData();
|
||||
await loadPermintaanPenjadwalanData();
|
||||
|
@ -75,15 +75,6 @@ class PenitipanBantuanController extends GetxController {
|
||||
|
||||
// Hapus delay dan muat data petugas desa langsung
|
||||
loadAllPetugasDesaData();
|
||||
|
||||
// Listener untuk pencarian donatur
|
||||
donaturSearchController.addListener(() {
|
||||
if (donaturSearchController.text.length >= 3) {
|
||||
searchDonatur(donaturSearchController.text);
|
||||
} else {
|
||||
hasilPencarianDonatur.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -441,16 +432,19 @@ class PenitipanBantuanController extends GetxController {
|
||||
|
||||
Future<DonaturModel?> getDonaturInfo(String donaturId) async {
|
||||
try {
|
||||
// Cek cache terlebih dahulu
|
||||
// Periksa apakah donatur sudah ada di cache
|
||||
if (donaturCache.containsKey(donaturId)) {
|
||||
return donaturCache[donaturId];
|
||||
}
|
||||
|
||||
final donaturData = await _supabaseService.getDonaturById(donaturId);
|
||||
if (donaturData != null) {
|
||||
final donatur = DonaturModel.fromJson(donaturData);
|
||||
// Ambil data donatur dari server
|
||||
final result = await _supabaseService.getDonaturById(donaturId);
|
||||
if (result != null) {
|
||||
final donatur = DonaturModel.fromJson(result);
|
||||
|
||||
// Simpan ke cache
|
||||
donaturCache[donaturId] = donatur;
|
||||
|
||||
return donatur;
|
||||
}
|
||||
return null;
|
||||
@ -595,16 +589,12 @@ class PenitipanBantuanController extends GetxController {
|
||||
}
|
||||
|
||||
String getPetugasDesaNama(String? petugasDesaId) {
|
||||
print('Petugas Desa ID: $petugasDesaId');
|
||||
if (petugasDesaId == null) {
|
||||
return 'Tidak diketahui';
|
||||
}
|
||||
|
||||
// Cek apakah data ada di cache
|
||||
if (!petugasDesaCache.containsKey(petugasDesaId)) {
|
||||
print(
|
||||
'Data petugas desa tidak ditemukan di cache untuk ID: $petugasDesaId');
|
||||
// Muat data petugas dan perbarui UI
|
||||
loadPetugasDesaData(petugasDesaId);
|
||||
|
||||
// Coba cek lagi setelah pemuatan
|
||||
@ -620,24 +610,18 @@ class PenitipanBantuanController extends GetxController {
|
||||
// Sekarang data seharusnya ada di cache
|
||||
// Akses nama dari struktur data petugas_desa
|
||||
final nama = petugasDesaCache[petugasDesaId]?['nama_lengkap'];
|
||||
print('Nama petugas desa: $nama untuk ID: $petugasDesaId');
|
||||
return nama ?? 'Tidak diketahui';
|
||||
}
|
||||
|
||||
// Fungsi untuk memuat data petugas desa dan memperbarui UI
|
||||
Future<void> loadPetugasDesaData(String petugasDesaId) async {
|
||||
try {
|
||||
print('Memuat data petugas desa untuk ID: $petugasDesaId');
|
||||
final petugasData = await getPetugasDesaInfo(petugasDesaId);
|
||||
if (petugasData != null) {
|
||||
// Data sudah dimasukkan ke cache oleh getPetugasDesaInfo
|
||||
print('Berhasil memuat data petugas: ${petugasData['nama_lengkap']}');
|
||||
|
||||
// Refresh UI segera
|
||||
update(['petugas_data']);
|
||||
} else {
|
||||
print(
|
||||
'Gagal mengambil data petugas desa dari server untuk ID: $petugasDesaId');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error saat memuat data petugas desa: $e');
|
||||
@ -647,11 +631,9 @@ class PenitipanBantuanController extends GetxController {
|
||||
// Fungsi untuk memuat semua data petugas desa yang terkait dengan penitipan
|
||||
void loadAllPetugasDesaData() async {
|
||||
try {
|
||||
print('Memuat ulang semua data petugas desa...');
|
||||
for (var item in daftarPenitipan) {
|
||||
if (item.status == 'TERVERIFIKASI' && item.petugasDesaId != null) {
|
||||
if (!petugasDesaCache.containsKey(item.petugasDesaId)) {
|
||||
print('Memuat data petugas desa untuk ID: ${item.petugasDesaId}');
|
||||
await getPetugasDesaInfo(item.petugasDesaId);
|
||||
}
|
||||
}
|
||||
@ -669,76 +651,6 @@ class PenitipanBantuanController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> searchDonatur(String keyword) async {
|
||||
if (keyword.length < 3) {
|
||||
hasilPencarianDonatur.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
isSearchingDonatur.value = true;
|
||||
try {
|
||||
final result = await _supabaseService.searchDonatur(keyword);
|
||||
if (result != null) {
|
||||
hasilPencarianDonatur.value =
|
||||
result.map((data) => DonaturModel.fromJson(data)).toList();
|
||||
} else {
|
||||
hasilPencarianDonatur.clear();
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error searching donatur: $e');
|
||||
hasilPencarianDonatur.clear();
|
||||
} finally {
|
||||
isSearchingDonatur.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Metode untuk mendapatkan daftar donatur
|
||||
Future<List<DonaturModel>> getDaftarDonatur() async {
|
||||
try {
|
||||
final result = await _supabaseService.getDaftarDonatur();
|
||||
if (result != null) {
|
||||
return result.map((data) => DonaturModel.fromJson(data)).toList();
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
print('Error getting daftar donatur: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> tambahDonatur({
|
||||
required String nama,
|
||||
required String noHp,
|
||||
String? alamat,
|
||||
String? email,
|
||||
String? jenis,
|
||||
}) async {
|
||||
try {
|
||||
final donaturData = {
|
||||
'nama_lengkap': nama,
|
||||
'no_hp': noHp,
|
||||
'alamat': alamat,
|
||||
'email': email,
|
||||
'jenis': jenis,
|
||||
'status': 'AKTIF',
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
return await _supabaseService.tambahDonatur(donaturData);
|
||||
} catch (e) {
|
||||
print('Error adding donatur: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal menambahkan donatur: ${e.toString()}',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Mendapatkan informasi apakah stok bantuan berupa uang
|
||||
bool isStokBantuanUang(String stokBantuanId) {
|
||||
if (!stokBantuanMap.containsKey(stokBantuanId)) {
|
||||
@ -772,4 +684,10 @@ class PenitipanBantuanController extends GetxController {
|
||||
print(
|
||||
'Counter updated - Menunggu: $menunggu, Terverifikasi: $terverifikasi, Ditolak: $ditolak');
|
||||
}
|
||||
|
||||
// Metode untuk membersihkan pencarian donatur
|
||||
void resetDonaturSearch() {
|
||||
hasilPencarianDonatur.clear();
|
||||
donaturSearchController.clear();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,305 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/data/models/riwayat_stok_model.dart';
|
||||
import 'package:penyaluran_app/app/data/models/stok_bantuan_model.dart';
|
||||
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
|
||||
class RiwayatStokController extends GetxController {
|
||||
final AuthController _authController = Get.find<AuthController>();
|
||||
final SupabaseService _supabaseService = SupabaseService.to;
|
||||
final ImagePicker _imagePicker = ImagePicker();
|
||||
|
||||
final RxBool isLoading = false.obs;
|
||||
final RxList<RiwayatStokModel> daftarRiwayatStok = <RiwayatStokModel>[].obs;
|
||||
final RxList<StokBantuanModel> daftarStokBantuan = <StokBantuanModel>[].obs;
|
||||
|
||||
// Filter untuk riwayat stok
|
||||
final RxString filterJenisPerubahan = 'semua'.obs;
|
||||
final RxString filterStokBantuanId = 'semua'.obs;
|
||||
|
||||
// Controller untuk pencarian
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
final RxString searchQuery = ''.obs;
|
||||
|
||||
// Data untuk form penambahan/pengurangan manual
|
||||
final Rx<StokBantuanModel?> selectedStokBantuan = Rx<StokBantuanModel?>(null);
|
||||
final RxDouble jumlah = 0.0.obs;
|
||||
final RxString alasan = ''.obs;
|
||||
final Rx<File?> fotoBukti = Rx<File?>(null);
|
||||
final RxBool isSubmitting = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
loadRiwayatStok();
|
||||
loadStokBantuan();
|
||||
|
||||
// Listener untuk pencarian
|
||||
searchController.addListener(() {
|
||||
searchQuery.value = searchController.text;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
searchController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// Metode untuk memperbarui data saat tab diaktifkan kembali
|
||||
void onTabReactivated() {
|
||||
refreshData();
|
||||
}
|
||||
|
||||
Future<void> loadRiwayatStok() async {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
final String? stokBantuanId = filterStokBantuanId.value != 'semua'
|
||||
? filterStokBantuanId.value
|
||||
: null;
|
||||
|
||||
final String? jenisPerubahan = filterJenisPerubahan.value != 'semua'
|
||||
? filterJenisPerubahan.value
|
||||
: null;
|
||||
|
||||
final riwayatStokData = await _supabaseService.getRiwayatStok(
|
||||
stokBantuanId: stokBantuanId,
|
||||
jenisPerubahan: jenisPerubahan,
|
||||
);
|
||||
|
||||
if (riwayatStokData != null) {
|
||||
daftarRiwayatStok.value = riwayatStokData
|
||||
.map((data) => RiwayatStokModel.fromJson(data))
|
||||
.toList();
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading riwayat stok data: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal memuat data riwayat stok: $e',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadStokBantuan() async {
|
||||
try {
|
||||
final stokBantuanData = await _supabaseService.getStokBantuan();
|
||||
if (stokBantuanData != null) {
|
||||
daftarStokBantuan.value = stokBantuanData
|
||||
.map((data) => StokBantuanModel.fromJson(data))
|
||||
.toList();
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading stok bantuan data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refreshData() async {
|
||||
await loadRiwayatStok();
|
||||
await loadStokBantuan();
|
||||
}
|
||||
|
||||
void filterByJenisPerubahan(String value) {
|
||||
filterJenisPerubahan.value = value;
|
||||
loadRiwayatStok();
|
||||
}
|
||||
|
||||
void filterByStokBantuan(String value) {
|
||||
filterStokBantuanId.value = value;
|
||||
loadRiwayatStok();
|
||||
}
|
||||
|
||||
List<RiwayatStokModel> getFilteredRiwayatStok() {
|
||||
if (searchQuery.isEmpty) {
|
||||
return daftarRiwayatStok;
|
||||
}
|
||||
|
||||
return daftarRiwayatStok.where((item) {
|
||||
// Cari berdasarkan nama stok bantuan
|
||||
final stokBantuanMatch = item.stokBantuan != null &&
|
||||
item.stokBantuan!['nama'] != null &&
|
||||
item.stokBantuan!['nama']
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.contains(searchQuery.value.toLowerCase());
|
||||
|
||||
// Cari berdasarkan alasan
|
||||
final alasanMatch = item.alasan != null &&
|
||||
item.alasan!.toLowerCase().contains(searchQuery.value.toLowerCase());
|
||||
|
||||
// Cari berdasarkan sumber
|
||||
final sumberMatch = item.sumber != null &&
|
||||
item.sumber!.toLowerCase().contains(searchQuery.value.toLowerCase());
|
||||
|
||||
return stokBantuanMatch || alasanMatch || sumberMatch;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<void> tambahStokManual() async {
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
if (selectedStokBantuan.value == null) {
|
||||
throw Exception('Pilih bantuan terlebih dahulu');
|
||||
}
|
||||
|
||||
if (jumlah.value <= 0) {
|
||||
throw Exception('Jumlah harus lebih dari 0');
|
||||
}
|
||||
|
||||
if (alasan.value.isEmpty) {
|
||||
throw Exception('Alasan harus diisi');
|
||||
}
|
||||
|
||||
if (fotoBukti.value == null) {
|
||||
throw Exception('Foto bukti harus diupload');
|
||||
}
|
||||
|
||||
final petugasId = _authController.baseUser?.id;
|
||||
if (petugasId == null) {
|
||||
throw Exception('ID petugas tidak ditemukan');
|
||||
}
|
||||
|
||||
await _supabaseService.tambahStokManual(
|
||||
stokBantuanId: selectedStokBantuan.value!.id!,
|
||||
jumlah: jumlah.value,
|
||||
alasan: alasan.value,
|
||||
fotoBuktiPath: fotoBukti.value!.path,
|
||||
petugasId: petugasId,
|
||||
);
|
||||
|
||||
// Reset form
|
||||
resetForm();
|
||||
|
||||
// Refresh data
|
||||
await refreshData();
|
||||
|
||||
Get.back(); // Tutup dialog
|
||||
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Stok bantuan berhasil ditambahkan',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error menambahkan stok manual: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal menambahkan stok: ${e.toString()}',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> kurangiStokManual() async {
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
if (selectedStokBantuan.value == null) {
|
||||
throw Exception('Pilih bantuan terlebih dahulu');
|
||||
}
|
||||
|
||||
if (jumlah.value <= 0) {
|
||||
throw Exception('Jumlah harus lebih dari 0');
|
||||
}
|
||||
|
||||
if (alasan.value.isEmpty) {
|
||||
throw Exception('Alasan harus diisi');
|
||||
}
|
||||
|
||||
if (fotoBukti.value == null) {
|
||||
throw Exception('Foto bukti harus diupload');
|
||||
}
|
||||
|
||||
final petugasId = _authController.baseUser?.id;
|
||||
if (petugasId == null) {
|
||||
throw Exception('ID petugas tidak ditemukan');
|
||||
}
|
||||
|
||||
await _supabaseService.kurangiStokManual(
|
||||
stokBantuanId: selectedStokBantuan.value!.id!,
|
||||
jumlah: jumlah.value,
|
||||
alasan: alasan.value,
|
||||
fotoBuktiPath: fotoBukti.value!.path,
|
||||
petugasId: petugasId,
|
||||
);
|
||||
|
||||
// Reset form
|
||||
resetForm();
|
||||
|
||||
// Refresh data
|
||||
await refreshData();
|
||||
|
||||
Get.back(); // Tutup dialog
|
||||
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Stok bantuan berhasil dikurangi',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error mengurangi stok manual: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal mengurangi stok: ${e.toString()}',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void setSelectedStokBantuan(StokBantuanModel? stokBantuan) {
|
||||
selectedStokBantuan.value = stokBantuan;
|
||||
}
|
||||
|
||||
void setJumlah(double value) {
|
||||
jumlah.value = value;
|
||||
}
|
||||
|
||||
void setAlasan(String value) {
|
||||
alasan.value = value;
|
||||
}
|
||||
|
||||
Future<void> pickImage() async {
|
||||
try {
|
||||
final pickedFile =
|
||||
await _imagePicker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
fotoBukti.value = File(pickedFile.path);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error picking image: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal memilih gambar: ${e.toString()}',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void resetForm() {
|
||||
selectedStokBantuan.value = null;
|
||||
jumlah.value = 0.0;
|
||||
alasan.value = '';
|
||||
fotoBukti.value = null;
|
||||
}
|
||||
}
|
@ -249,21 +249,24 @@ class StokBantuanController extends GetxController {
|
||||
return filteredList;
|
||||
}
|
||||
|
||||
// Metode untuk mendapatkan jumlah stok yang hampir habis (stok <= 10)
|
||||
// Metode untuk mendapatkan jumlah stok yang hampir habis
|
||||
int getStokHampirHabis() {
|
||||
return daftarStokBantuan
|
||||
.where((stok) => (stok.totalStok ?? 0) <= 10)
|
||||
.where((item) => (item.totalStok ?? 0) <= 10)
|
||||
.length;
|
||||
}
|
||||
|
||||
// Metode untuk menghitung total dana bantuan
|
||||
// Metode untuk menghitung total dana bantuan dari stok uang
|
||||
void _hitungTotalDanaBantuan() {
|
||||
double total = 0.0;
|
||||
|
||||
// Hitung dari stok yang isUang = true
|
||||
for (var stok in daftarStokBantuan) {
|
||||
if (stok.isUang == true) {
|
||||
total += stok.totalStok ?? 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
totalDanaBantuan.value = total;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user