Tambahkan dukungan penyimpanan lokal dan perbaikan manajemen data
- Integrasikan GetStorage untuk menyimpan data counter secara lokal - Tambahkan metode loadCountersFromStorage di CounterService - Perbarui model DonaturModel dan StokBantuanModel untuk konsistensi data - Tambahkan properti lastUpdateTime di controller untuk melacak pembaruan data - Perbaiki tampilan dengan menambahkan informasi waktu terakhir update - Optimalkan metode refresh dan update data di berbagai controller
This commit is contained in:
@ -5,10 +5,8 @@ class DonaturModel {
|
|||||||
final String? nama;
|
final String? nama;
|
||||||
final String? alamat;
|
final String? alamat;
|
||||||
final String? telepon;
|
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;
|
||||||
@ -18,10 +16,8 @@ class DonaturModel {
|
|||||||
this.nama,
|
this.nama,
|
||||||
this.alamat,
|
this.alamat,
|
||||||
this.telepon,
|
this.telepon,
|
||||||
this.noHp,
|
|
||||||
this.email,
|
this.email,
|
||||||
this.jenis,
|
this.jenis,
|
||||||
this.deskripsi,
|
|
||||||
this.status,
|
this.status,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
@ -37,10 +33,8 @@ class DonaturModel {
|
|||||||
nama: json["nama"],
|
nama: json["nama"],
|
||||||
alamat: json["alamat"],
|
alamat: json["alamat"],
|
||||||
telepon: json["telepon"],
|
telepon: json["telepon"],
|
||||||
noHp: json["no_hp"] ?? json["telepon"],
|
|
||||||
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"])
|
||||||
@ -55,10 +49,8 @@ class DonaturModel {
|
|||||||
"nama": nama,
|
"nama": nama,
|
||||||
"alamat": alamat,
|
"alamat": alamat,
|
||||||
"telepon": telepon,
|
"telepon": telepon,
|
||||||
"no_hp": noHp ?? telepon,
|
|
||||||
"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(),
|
||||||
|
@ -36,7 +36,11 @@ class StokBantuanModel {
|
|||||||
nama: json["nama"],
|
nama: json["nama"],
|
||||||
kategoriBantuanId: json["kategori_bantuan_id"],
|
kategoriBantuanId: json["kategori_bantuan_id"],
|
||||||
kategoriBantuan: json["kategori_bantuan"],
|
kategoriBantuan: json["kategori_bantuan"],
|
||||||
totalStok: 0.0,
|
totalStok: json["total_stok"] != null
|
||||||
|
? (json["total_stok"] is int
|
||||||
|
? json["total_stok"].toDouble()
|
||||||
|
: json["total_stok"])
|
||||||
|
: 0.0,
|
||||||
satuan: json["satuan"],
|
satuan: json["satuan"],
|
||||||
deskripsi: json["deskripsi"],
|
deskripsi: json["deskripsi"],
|
||||||
createdAt: json["created_at"] != null
|
createdAt: json["created_at"] != null
|
||||||
@ -64,6 +68,11 @@ class StokBantuanModel {
|
|||||||
data["id"] = id;
|
data["id"] = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tambahkan total_stok hanya jika tidak null
|
||||||
|
if (totalStok != null) {
|
||||||
|
data["total_stok"] = totalStok;
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,21 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get_storage/get_storage.dart';
|
||||||
|
|
||||||
/// Service untuk berbagi data counter antar controller
|
/// Service untuk berbagi data counter antar controller
|
||||||
class CounterService extends GetxService {
|
class CounterService extends GetxService {
|
||||||
static CounterService get to => Get.find<CounterService>();
|
static CounterService get to => Get.find<CounterService>();
|
||||||
|
|
||||||
|
// Penyimpanan lokal
|
||||||
|
final GetStorage _storage = GetStorage();
|
||||||
|
|
||||||
|
// Keys untuk penyimpanan
|
||||||
|
static const String _keyMenunggu = 'counter_menunggu';
|
||||||
|
static const String _keyTerverifikasi = 'counter_terverifikasi';
|
||||||
|
static const String _keyDitolak = 'counter_ditolak';
|
||||||
|
static const String _keyDiproses = 'counter_diproses';
|
||||||
|
static const String _keyNotifikasi = 'counter_notifikasi';
|
||||||
|
static const String _keyJadwal = 'counter_jadwal';
|
||||||
|
|
||||||
// Counter untuk penitipan
|
// Counter untuk penitipan
|
||||||
final RxInt jumlahMenunggu = 0.obs;
|
final RxInt jumlahMenunggu = 0.obs;
|
||||||
final RxInt jumlahTerverifikasi = 0.obs;
|
final RxInt jumlahTerverifikasi = 0.obs;
|
||||||
@ -18,6 +30,26 @@ class CounterService extends GetxService {
|
|||||||
// Counter untuk jadwal
|
// Counter untuk jadwal
|
||||||
final RxInt jumlahJadwalHariIni = 0.obs;
|
final RxInt jumlahJadwalHariIni = 0.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
// Muat nilai counter dari penyimpanan lokal
|
||||||
|
loadCountersFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk memuat counter dari penyimpanan lokal
|
||||||
|
void loadCountersFromStorage() {
|
||||||
|
jumlahMenunggu.value = _storage.read(_keyMenunggu) ?? 0;
|
||||||
|
jumlahTerverifikasi.value = _storage.read(_keyTerverifikasi) ?? 0;
|
||||||
|
jumlahDitolak.value = _storage.read(_keyDitolak) ?? 0;
|
||||||
|
jumlahDiproses.value = _storage.read(_keyDiproses) ?? 0;
|
||||||
|
jumlahNotifikasiBelumDibaca.value = _storage.read(_keyNotifikasi) ?? 0;
|
||||||
|
jumlahJadwalHariIni.value = _storage.read(_keyJadwal) ?? 0;
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Counter loaded from storage - Menunggu: ${jumlahMenunggu.value}, Terverifikasi: ${jumlahTerverifikasi.value}, Ditolak: ${jumlahDitolak.value}');
|
||||||
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui counter penitipan
|
// Metode untuk memperbarui counter penitipan
|
||||||
void updatePenitipanCounters({
|
void updatePenitipanCounters({
|
||||||
required int menunggu,
|
required int menunggu,
|
||||||
@ -27,20 +59,31 @@ class CounterService extends GetxService {
|
|||||||
jumlahMenunggu.value = menunggu;
|
jumlahMenunggu.value = menunggu;
|
||||||
jumlahTerverifikasi.value = terverifikasi;
|
jumlahTerverifikasi.value = terverifikasi;
|
||||||
jumlahDitolak.value = ditolak;
|
jumlahDitolak.value = ditolak;
|
||||||
|
|
||||||
|
// Simpan ke penyimpanan lokal
|
||||||
|
_storage.write(_keyMenunggu, menunggu);
|
||||||
|
_storage.write(_keyTerverifikasi, terverifikasi);
|
||||||
|
_storage.write(_keyDitolak, ditolak);
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Counter updated and saved - Menunggu: $menunggu, Terverifikasi: $terverifikasi, Ditolak: $ditolak');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui counter pengaduan
|
// Metode untuk memperbarui counter pengaduan
|
||||||
void updatePengaduanCounter(int diproses) {
|
void updatePengaduanCounter(int diproses) {
|
||||||
jumlahDiproses.value = diproses;
|
jumlahDiproses.value = diproses;
|
||||||
|
_storage.write(_keyDiproses, diproses);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui counter notifikasi
|
// Metode untuk memperbarui counter notifikasi
|
||||||
void updateNotifikasiCounter(int belumDibaca) {
|
void updateNotifikasiCounter(int belumDibaca) {
|
||||||
jumlahNotifikasiBelumDibaca.value = belumDibaca;
|
jumlahNotifikasiBelumDibaca.value = belumDibaca;
|
||||||
|
_storage.write(_keyNotifikasi, belumDibaca);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk memperbarui counter jadwal
|
// Metode untuk memperbarui counter jadwal
|
||||||
void updateJadwalCounter(int hariIni) {
|
void updateJadwalCounter(int hariIni) {
|
||||||
jumlahJadwalHariIni.value = hariIni;
|
jumlahJadwalHariIni.value = hariIni;
|
||||||
|
_storage.write(_keyJadwal, hariIni);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,9 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
// Controller untuk pencarian
|
// Controller untuk pencarian
|
||||||
final TextEditingController searchController = TextEditingController();
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
// Tambahkan properti untuk waktu terakhir update
|
||||||
|
Rx<DateTime> lastUpdateTime = DateTime.now().obs;
|
||||||
|
|
||||||
UserModel? get user => _authController.user;
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
// Getter untuk counter dari CounterService
|
// Getter untuk counter dari CounterService
|
||||||
@ -75,6 +78,13 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onReady() {
|
||||||
|
super.onReady();
|
||||||
|
// Pastikan counter diperbarui saat tab diakses kembali
|
||||||
|
updateCounters();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
searchController.dispose();
|
searchController.dispose();
|
||||||
@ -82,6 +92,13 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memperbarui data saat tab diakses kembali
|
||||||
|
void onTabReactivated() {
|
||||||
|
print('Penitipan tab reactivated - refreshing data');
|
||||||
|
// Selalu muat ulang data dari server saat tab diaktifkan kembali
|
||||||
|
refreshData();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadPenitipanData() async {
|
Future<void> loadPenitipanData() async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -92,20 +109,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// Hitung jumlah berdasarkan status
|
// Hitung jumlah berdasarkan status
|
||||||
int menunggu =
|
updateCounters();
|
||||||
daftarPenitipan.where((item) => item.status == 'MENUNGGU').length;
|
|
||||||
int terverifikasi = daftarPenitipan
|
|
||||||
.where((item) => item.status == 'TERVERIFIKASI')
|
|
||||||
.length;
|
|
||||||
int ditolak =
|
|
||||||
daftarPenitipan.where((item) => item.status == 'DITOLAK').length;
|
|
||||||
|
|
||||||
// Update counter di CounterService
|
|
||||||
_counterService.updatePenitipanCounters(
|
|
||||||
menunggu: menunggu,
|
|
||||||
terverifikasi: terverifikasi,
|
|
||||||
ditolak: ditolak,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Muat informasi petugas desa untuk item yang terverifikasi
|
// Muat informasi petugas desa untuk item yang terverifikasi
|
||||||
print(
|
print(
|
||||||
@ -130,6 +134,9 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
petugasDesaCache.forEach((key, value) {
|
petugasDesaCache.forEach((key, value) {
|
||||||
print('ID: $key, Nama: ${value['name']}');
|
print('ID: $key, Nama: ${value['name']}');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update waktu terakhir refresh
|
||||||
|
lastUpdateTime.value = DateTime.now();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading penitipan data: $e');
|
print('Error loading penitipan data: $e');
|
||||||
@ -268,6 +275,9 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
fotoBantuanPaths.clear();
|
fotoBantuanPaths.clear();
|
||||||
|
|
||||||
await loadPenitipanData();
|
await loadPenitipanData();
|
||||||
|
// Pastikan counter diperbarui setelah penambahan
|
||||||
|
updateCounters();
|
||||||
|
|
||||||
Get.back(); // Tutup dialog
|
Get.back(); // Tutup dialog
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
@ -313,6 +323,9 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
fotoBuktiSerahTerimaPath.value = null;
|
fotoBuktiSerahTerimaPath.value = null;
|
||||||
|
|
||||||
await loadPenitipanData();
|
await loadPenitipanData();
|
||||||
|
// Pastikan counter diperbarui setelah verifikasi
|
||||||
|
updateCounters();
|
||||||
|
|
||||||
Get.back(); // Tutup dialog
|
Get.back(); // Tutup dialog
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
@ -341,6 +354,9 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
await _supabaseService.tolakPenitipan(penitipanId, alasan);
|
await _supabaseService.tolakPenitipan(penitipanId, alasan);
|
||||||
await loadPenitipanData();
|
await loadPenitipanData();
|
||||||
|
// Pastikan counter diperbarui setelah penolakan
|
||||||
|
updateCounters();
|
||||||
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penitipan berhasil ditolak',
|
'Penitipan berhasil ditolak',
|
||||||
@ -414,7 +430,10 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
|
|
||||||
Future<void> refreshData() async {
|
Future<void> refreshData() async {
|
||||||
await loadPenitipanData();
|
await loadPenitipanData();
|
||||||
await loadKategoriBantuanData();
|
await loadStokBantuanData();
|
||||||
|
|
||||||
|
// Update waktu terakhir refresh
|
||||||
|
lastUpdateTime.value = DateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeCategory(int index) {
|
void changeCategory(int index) {
|
||||||
@ -615,16 +634,19 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
|
|
||||||
Future<String?> tambahDonatur({
|
Future<String?> tambahDonatur({
|
||||||
required String nama,
|
required String nama,
|
||||||
required String noHp,
|
required String telepon,
|
||||||
String? alamat,
|
String? alamat,
|
||||||
String? email,
|
String? email,
|
||||||
|
String? jenis,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final donaturData = {
|
final donaturData = {
|
||||||
'nama': nama,
|
'nama': nama,
|
||||||
'no_hp': noHp,
|
'telepon': telepon,
|
||||||
'alamat': alamat,
|
'alamat': alamat,
|
||||||
'email': email,
|
'email': email,
|
||||||
|
'jenis': jenis,
|
||||||
|
'status': 'AKTIF',
|
||||||
'created_at': DateTime.now().toIso8601String(),
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
'updated_at': DateTime.now().toIso8601String(),
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
};
|
};
|
||||||
@ -650,4 +672,25 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
}
|
}
|
||||||
return stokBantuanMap[stokBantuanId]?.isUang ?? false;
|
return stokBantuanMap[stokBantuanId]?.isUang ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode baru untuk memperbarui counter
|
||||||
|
void updateCounters() {
|
||||||
|
int menunggu =
|
||||||
|
daftarPenitipan.where((item) => item.status == 'MENUNGGU').length;
|
||||||
|
int terverifikasi =
|
||||||
|
daftarPenitipan.where((item) => item.status == 'TERVERIFIKASI').length;
|
||||||
|
int ditolak =
|
||||||
|
daftarPenitipan.where((item) => item.status == 'DITOLAK').length;
|
||||||
|
|
||||||
|
// Update counter di CounterService
|
||||||
|
_counterService.updatePenitipanCounters(
|
||||||
|
menunggu: menunggu,
|
||||||
|
terverifikasi: terverifikasi,
|
||||||
|
ditolak: ditolak,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Debug counter values
|
||||||
|
print(
|
||||||
|
'Counter updated - Menunggu: $menunggu, Terverifikasi: $terverifikasi, Ditolak: $ditolak');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import 'package:penyaluran_app/app/data/models/user_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';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/stok_bantuan_controller.dart';
|
||||||
|
|
||||||
class PetugasDesaController extends GetxController {
|
class PetugasDesaController extends GetxController {
|
||||||
final AuthController _authController = Get.find<AuthController>();
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
@ -220,10 +222,28 @@ class PetugasDesaController extends GetxController {
|
|||||||
|
|
||||||
// Jika tab penitipan dipilih, muat ulang data penitipan
|
// Jika tab penitipan dipilih, muat ulang data penitipan
|
||||||
if (index == 2) {
|
if (index == 2) {
|
||||||
|
// Dapatkan instance PenitipanBantuanController dan panggil onTabReactivated
|
||||||
|
try {
|
||||||
|
final penitipanController = Get.find<PenitipanBantuanController>();
|
||||||
|
penitipanController.onTabReactivated();
|
||||||
|
print('Memanggil onTabReactivated pada PenitipanBantuanController');
|
||||||
|
} catch (e) {
|
||||||
|
print('Error saat memanggil onTabReactivated: $e');
|
||||||
|
// Fallback ke metode lama jika controller tidak ditemukan
|
||||||
loadPenitipanData();
|
loadPenitipanData();
|
||||||
|
}
|
||||||
} else if (index == 3) {
|
} else if (index == 3) {
|
||||||
// Jika tab pengaduan dipilih, muat ulang data pengaduan
|
// Jika tab pengaduan dipilih, muat ulang data pengaduan
|
||||||
loadPengaduanData();
|
loadPengaduanData();
|
||||||
|
} else if (index == 4) {
|
||||||
|
// Jika tab stok bantuan dipilih, muat ulang data stok bantuan
|
||||||
|
try {
|
||||||
|
final stokBantuanController = Get.find<StokBantuanController>();
|
||||||
|
stokBantuanController.onTabReactivated();
|
||||||
|
print('Memanggil onTabReactivated pada StokBantuanController');
|
||||||
|
} catch (e) {
|
||||||
|
print('Error saat memanggil onTabReactivated: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,9 @@ class StokBantuanController extends GetxController {
|
|||||||
// Tambahkan properti untuk total dana bantuan
|
// Tambahkan properti untuk total dana bantuan
|
||||||
RxDouble totalDanaBantuan = 0.0.obs;
|
RxDouble totalDanaBantuan = 0.0.obs;
|
||||||
|
|
||||||
|
// Tambahkan properti untuk waktu terakhir update
|
||||||
|
Rx<DateTime> lastUpdateTime = DateTime.now().obs;
|
||||||
|
|
||||||
UserModel? get user => _authController.user;
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -54,6 +57,12 @@ class StokBantuanController extends GetxController {
|
|||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memperbarui data saat tab diaktifkan kembali
|
||||||
|
void onTabReactivated() {
|
||||||
|
print('Stok Bantuan tab reactivated - refreshing data');
|
||||||
|
refreshData();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadStokBantuanData() async {
|
Future<void> loadStokBantuanData() async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -65,6 +74,9 @@ class StokBantuanController extends GetxController {
|
|||||||
|
|
||||||
// Hitung total dana bantuan
|
// Hitung total dana bantuan
|
||||||
_hitungTotalDanaBantuan();
|
_hitungTotalDanaBantuan();
|
||||||
|
|
||||||
|
// Update waktu terakhir refresh
|
||||||
|
lastUpdateTime.value = DateTime.now();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading stok bantuan data: $e');
|
print('Error loading stok bantuan data: $e');
|
||||||
@ -79,65 +91,14 @@ class StokBantuanController extends GetxController {
|
|||||||
await _supabaseService.getPenitipanBantuanTerverifikasi();
|
await _supabaseService.getPenitipanBantuanTerverifikasi();
|
||||||
if (penitipanData != null) {
|
if (penitipanData != null) {
|
||||||
daftarPenitipanTerverifikasi.value = penitipanData;
|
daftarPenitipanTerverifikasi.value = penitipanData;
|
||||||
// Update total stok berdasarkan penitipan terverifikasi
|
// Tidak perlu lagi menghitung total stok dari penitipan
|
||||||
_hitungTotalStokDariPenitipan();
|
// karena total_stok sudah dikelola oleh trigger database
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading penitipan terverifikasi: $e');
|
print('Error loading penitipan terverifikasi: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk menghitung total stok dari penitipan terverifikasi
|
|
||||||
void _hitungTotalStokDariPenitipan() {
|
|
||||||
// Buat map untuk menyimpan total stok per stok_bantuan_id
|
|
||||||
Map<String, double> totalStokMap = {};
|
|
||||||
|
|
||||||
// Hitung total stok dari penitipan terverifikasi
|
|
||||||
for (var penitipan in daftarPenitipanTerverifikasi) {
|
|
||||||
String? stokBantuanId = penitipan['stok_bantuan_id'];
|
|
||||||
double jumlah = penitipan['jumlah'] != null
|
|
||||||
? (penitipan['jumlah'] is int
|
|
||||||
? penitipan['jumlah'].toDouble()
|
|
||||||
: penitipan['jumlah'])
|
|
||||||
: 0.0;
|
|
||||||
|
|
||||||
if (stokBantuanId != null) {
|
|
||||||
if (totalStokMap.containsKey(stokBantuanId)) {
|
|
||||||
totalStokMap[stokBantuanId] =
|
|
||||||
(totalStokMap[stokBantuanId] ?? 0) + jumlah;
|
|
||||||
} else {
|
|
||||||
totalStokMap[stokBantuanId] = jumlah;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update total stok di daftarStokBantuan
|
|
||||||
for (var i = 0; i < daftarStokBantuan.length; i++) {
|
|
||||||
var stok = daftarStokBantuan[i];
|
|
||||||
if (stok.id != null) {
|
|
||||||
// Buat stok baru dengan total stok yang diperbarui
|
|
||||||
double newTotalStok = totalStokMap[stok.id] ?? 0.0;
|
|
||||||
|
|
||||||
daftarStokBantuan[i] = StokBantuanModel(
|
|
||||||
id: stok.id,
|
|
||||||
nama: stok.nama,
|
|
||||||
kategoriBantuanId: stok.kategoriBantuanId,
|
|
||||||
kategoriBantuan: stok.kategoriBantuan,
|
|
||||||
totalStok:
|
|
||||||
newTotalStok, // Gunakan nilai dari penitipan atau 0 jika tidak ada
|
|
||||||
satuan: stok.satuan,
|
|
||||||
deskripsi: stok.deskripsi,
|
|
||||||
createdAt: stok.createdAt,
|
|
||||||
updatedAt: stok.updatedAt,
|
|
||||||
isUang: stok.isUang,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hitung ulang total dana bantuan
|
|
||||||
_hitungTotalDanaBantuan();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadKategoriBantuanData() async {
|
Future<void> loadKategoriBantuanData() async {
|
||||||
try {
|
try {
|
||||||
final kategoriBantuanData = await _supabaseService.getKategoriBantuan();
|
final kategoriBantuanData = await _supabaseService.getKategoriBantuan();
|
||||||
@ -151,17 +112,14 @@ class StokBantuanController extends GetxController {
|
|||||||
|
|
||||||
Future<void> addStok(StokBantuanModel stok) async {
|
Future<void> addStok(StokBantuanModel stok) async {
|
||||||
try {
|
try {
|
||||||
// Buat data stok baru tanpa field total_stok
|
// Buat data stok baru
|
||||||
final stokData = stok.toJson();
|
final stokData = stok.toJson();
|
||||||
|
|
||||||
// Hapus field total_stok dari data yang akan dikirim ke database
|
// Tambahkan total_stok = 0 untuk stok baru
|
||||||
if (stokData.containsKey('total_stok')) {
|
stokData['total_stok'] = 0.0;
|
||||||
stokData.remove('total_stok');
|
|
||||||
}
|
|
||||||
|
|
||||||
await _supabaseService.addStok(stokData);
|
await _supabaseService.addStok(stokData);
|
||||||
await loadStokBantuanData();
|
await loadStokBantuanData();
|
||||||
await loadPenitipanTerverifikasi();
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Stok bantuan berhasil ditambahkan',
|
'Stok bantuan berhasil ditambahkan',
|
||||||
@ -187,13 +145,13 @@ class StokBantuanController extends GetxController {
|
|||||||
final stokData = stok.toJson();
|
final stokData = stok.toJson();
|
||||||
|
|
||||||
// Hapus field total_stok dari data yang akan dikirim ke database
|
// Hapus field total_stok dari data yang akan dikirim ke database
|
||||||
|
// karena total_stok dikelola oleh trigger database
|
||||||
if (stokData.containsKey('total_stok')) {
|
if (stokData.containsKey('total_stok')) {
|
||||||
stokData.remove('total_stok');
|
stokData.remove('total_stok');
|
||||||
}
|
}
|
||||||
|
|
||||||
await _supabaseService.updateStok(stok.id ?? '', stokData);
|
await _supabaseService.updateStok(stok.id ?? '', stokData);
|
||||||
await loadStokBantuanData();
|
await loadStokBantuanData();
|
||||||
await loadPenitipanTerverifikasi();
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Stok bantuan berhasil diperbarui',
|
'Stok bantuan berhasil diperbarui',
|
||||||
@ -217,7 +175,6 @@ class StokBantuanController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
await _supabaseService.deleteStok(id);
|
await _supabaseService.deleteStok(id);
|
||||||
await loadStokBantuanData(); // Ini akan memanggil _hitungTotalDanaBantuan()
|
await loadStokBantuanData(); // Ini akan memanggil _hitungTotalDanaBantuan()
|
||||||
await loadPenitipanTerverifikasi(); // Perbarui data penitipan terverifikasi
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Stok bantuan berhasil dihapus',
|
'Stok bantuan berhasil dihapus',
|
||||||
@ -241,6 +198,10 @@ class StokBantuanController extends GetxController {
|
|||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
await loadStokBantuanData();
|
await loadStokBantuanData();
|
||||||
await loadPenitipanTerverifikasi();
|
await loadPenitipanTerverifikasi();
|
||||||
|
|
||||||
|
// Update waktu terakhir refresh
|
||||||
|
lastUpdateTime.value = DateTime.now();
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,14 +267,6 @@ class StokBantuanController extends GetxController {
|
|||||||
totalDanaBantuan.value = total;
|
totalDanaBantuan.value = total;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _hitungTotalStok() async {
|
|
||||||
// Implementasi metode _hitungTotalStok
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _filterStokBantuan() async {
|
|
||||||
// Implementasi metode _filterStokBantuan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mengatur filter
|
// Metode untuk mengatur filter
|
||||||
void setFilter(String value) {
|
void setFilter(String value) {
|
||||||
filterValue.value = value;
|
filterValue.value = value;
|
||||||
|
@ -31,6 +31,9 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
// Filter dan pencarian
|
// Filter dan pencarian
|
||||||
_buildFilterSearch(context),
|
_buildFilterSearch(context),
|
||||||
|
|
||||||
|
// Informasi terakhir update
|
||||||
|
_buildLastUpdateInfo(context),
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Daftar penitipan
|
// Daftar penitipan
|
||||||
@ -43,7 +46,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () => _showTambahPenitipanDialog(context),
|
onPressed: () => _showTambahPenitipanDialog(context),
|
||||||
backgroundColor: AppTheme.primaryColor,
|
backgroundColor: AppTheme.primaryColor,
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add, color: Colors.white),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -388,25 +391,32 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildItemDetail(
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
context,
|
context,
|
||||||
icon: Icons.calendar_today,
|
icon: Icons.calendar_today,
|
||||||
label: 'Tanggal Penitipan',
|
label: 'Tanggal Dibuat',
|
||||||
value: DateFormatter.formatDateTime(item.tanggalPenitipan,
|
value: DateFormatter.formatDateTime(item.createdAt,
|
||||||
defaultValue: 'Tidak ada tanggal'),
|
defaultValue: 'Tidak ada tanggal'),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
// Tampilkan informasi petugas desa jika status terverifikasi
|
Expanded(
|
||||||
if (item.status == 'TERVERIFIKASI' &&
|
child: item.status == 'TERVERIFIKASI' &&
|
||||||
item.petugasDesaId != null) ...[
|
item.petugasDesaId != null
|
||||||
const SizedBox(height: 8),
|
? _buildItemDetail(
|
||||||
_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),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
|
||||||
// Tampilkan thumbnail foto bantuan jika ada
|
// Tampilkan thumbnail foto bantuan jika ada
|
||||||
if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty)
|
if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty)
|
||||||
@ -721,8 +731,8 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
'Diverifikasi Oleh',
|
'Diverifikasi Oleh',
|
||||||
controller.getPetugasDesaNama(item.petugasDesaId),
|
controller.getPetugasDesaNama(item.petugasDesaId),
|
||||||
),
|
),
|
||||||
_buildDetailItem('Tanggal Masuk',
|
_buildDetailItem('Tanggal Dibuat',
|
||||||
DateFormatter.formatDateTime(item.tanggalPenitipan)),
|
DateFormatter.formatDateTime(item.createdAt)),
|
||||||
if (item.alasanPenolakan != null &&
|
if (item.alasanPenolakan != null &&
|
||||||
item.alasanPenolakan!.isNotEmpty)
|
item.alasanPenolakan!.isNotEmpty)
|
||||||
_buildDetailItem('Alasan Penolakan', item.alasanPenolakan!),
|
_buildDetailItem('Alasan Penolakan', item.alasanPenolakan!),
|
||||||
@ -1029,7 +1039,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Tambah Penitipan Bantuan',
|
'Tambah Manual Penitipan Bantuan',
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@ -1163,8 +1173,9 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
if (selectedDonatur.value!.noHp != null)
|
if (selectedDonatur.value!.telepon !=
|
||||||
Text(selectedDonatur.value!.noHp!),
|
null)
|
||||||
|
Text(selectedDonatur.value!.telepon!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1228,8 +1239,8 @@ 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.noHp != null
|
subtitle: donatur.telepon != null
|
||||||
? Text(donatur.noHp!)
|
? Text(donatur.telepon!)
|
||||||
: null,
|
: null,
|
||||||
dense: true,
|
dense: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -1269,6 +1280,8 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text('Tambah Donatur Baru'),
|
label: const Text('Tambah Donatur Baru'),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 8),
|
||||||
foregroundColor: AppTheme.primaryColor,
|
foregroundColor: AppTheme.primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1542,9 +1555,10 @@ 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 noHpController = TextEditingController();
|
final TextEditingController teleponController = TextEditingController();
|
||||||
final TextEditingController alamatController = TextEditingController();
|
final TextEditingController alamatController = TextEditingController();
|
||||||
final TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
|
final TextEditingController jenisController = TextEditingController();
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
Dialog(
|
Dialog(
|
||||||
@ -1591,32 +1605,80 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// No HP
|
// Telepon
|
||||||
Text(
|
Text(
|
||||||
'Nomor HP',
|
'Nomor Telepon',
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: noHpController,
|
controller: teleponController,
|
||||||
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 HP',
|
hintText: 'Masukkan nomor telepon',
|
||||||
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 HP harus diisi';
|
return 'Nomor telepon harus diisi';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Jenis (opsional)
|
||||||
|
Text(
|
||||||
|
'Jenis Donatur (Opsional)',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
hint: const Text('Pilih jenis donatur'),
|
||||||
|
value: jenisController.text.isEmpty
|
||||||
|
? null
|
||||||
|
: jenisController.text,
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'Perorangan',
|
||||||
|
child: Text('Perorangan'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'Perusahaan',
|
||||||
|
child: Text('Perusahaan'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'Lembaga',
|
||||||
|
child: Text('Lembaga'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'Komunitas',
|
||||||
|
child: Text('Komunitas'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem<String>(
|
||||||
|
value: 'Lainnya',
|
||||||
|
child: Text('Lainnya'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
jenisController.text = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Alamat (opsional)
|
// Alamat (opsional)
|
||||||
Text(
|
Text(
|
||||||
'Alamat (Opsional)',
|
'Alamat (Opsional)',
|
||||||
@ -1671,13 +1733,16 @@ 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,
|
||||||
noHp: noHpController.text,
|
telepon: teleponController.text,
|
||||||
alamat: alamatController.text.isEmpty
|
alamat: alamatController.text.isEmpty
|
||||||
? null
|
? null
|
||||||
: alamatController.text,
|
: alamatController.text,
|
||||||
email: emailController.text.isEmpty
|
email: emailController.text.isEmpty
|
||||||
? null
|
? null
|
||||||
: emailController.text,
|
: emailController.text,
|
||||||
|
jenis: jenisController.text.isEmpty
|
||||||
|
? null
|
||||||
|
: jenisController.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (donaturId != null) {
|
if (donaturId != null) {
|
||||||
@ -1708,4 +1773,30 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tambahkan widget untuk menampilkan waktu terakhir update
|
||||||
|
Widget _buildLastUpdateInfo(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final lastUpdate = controller.lastUpdateTime.value;
|
||||||
|
final formattedDate = DateFormatter.formatDateTimeWithHour(lastUpdate);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.update, size: 16, color: Colors.grey[600]),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'Data terupdate: $formattedDate',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
// Filter dan pencarian
|
// Filter dan pencarian
|
||||||
_buildFilterSearch(context),
|
_buildFilterSearch(context),
|
||||||
|
|
||||||
|
// Informasi terakhir update
|
||||||
|
_buildLastUpdateInfo(context),
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Daftar stok bantuan
|
// Daftar stok bantuan
|
||||||
@ -74,7 +77,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Data stok diambil dari penitipan bantuan terverifikasi',
|
'Total stok diperbarui otomatis saat ada penitipan bantuan terverifikasi',
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Colors.white.withOpacity(0.8),
|
color: Colors.white.withOpacity(0.8),
|
||||||
),
|
),
|
||||||
@ -508,22 +511,42 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
String? selectedJenisBantuanId;
|
String? selectedJenisBantuanId;
|
||||||
bool isUang = false;
|
bool isUang = false;
|
||||||
|
|
||||||
showDialog(
|
Get.dialog(
|
||||||
context: context,
|
Dialog(
|
||||||
builder: (context) => StatefulBuilder(
|
insetPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
builder: (context, setState) => AlertDialog(
|
child: Padding(
|
||||||
title: const Text('Tambah Stok Bantuan'),
|
padding: const EdgeInsets.all(16.0),
|
||||||
content: Form(
|
child: StatefulBuilder(
|
||||||
|
builder: (context, setState) => Form(
|
||||||
key: formKey,
|
key: formKey,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
Text(
|
||||||
|
'Tambah Stok Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Nama Bantuan
|
||||||
|
Text(
|
||||||
|
'Nama Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: namaController,
|
controller: namaController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Nama Bantuan',
|
border: OutlineInputBorder(
|
||||||
border: OutlineInputBorder(),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
hintText: 'Masukkan nama bantuan',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
@ -533,13 +556,23 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
decoration: const InputDecoration(
|
// Kategori Bantuan
|
||||||
labelText: 'Kategori Bantuan',
|
Text(
|
||||||
border: OutlineInputBorder(),
|
'Kategori Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
hint: const Text('Pilih kategori bantuan'),
|
||||||
value: selectedJenisBantuanId,
|
value: selectedJenisBantuanId,
|
||||||
hint: const Text('Pilih Kategori Bantuan'),
|
|
||||||
items: controller.daftarKategoriBantuan
|
items: controller.daftarKategoriBantuan
|
||||||
.map((kategori) => DropdownMenuItem<String>(
|
.map((kategori) => DropdownMenuItem<String>(
|
||||||
value: kategori['id'],
|
value: kategori['id'],
|
||||||
@ -558,7 +591,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Tambahkan checkbox untuk menandai sebagai uang
|
// Checkbox untuk bantuan berbentuk uang
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
title: const Text('Bantuan Berbentuk Uang (Rupiah)'),
|
title: const Text('Bantuan Berbentuk Uang (Rupiah)'),
|
||||||
value: isUang,
|
value: isUang,
|
||||||
@ -573,205 +606,26 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
activeColor: AppTheme.primaryColor,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Hapus input jumlah/stok dan hanya tampilkan input satuan
|
// Satuan
|
||||||
TextFormField(
|
Text(
|
||||||
controller: satuanController,
|
'Satuan',
|
||||||
decoration: const InputDecoration(
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
labelText: 'Satuan',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
enabled: !isUang, // Disable jika berbentuk uang
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Satuan tidak boleh kosong';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: deskripsiController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Deskripsi',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
// Tambahkan informasi tentang total stok
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.blue.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(color: Colors.blue.withOpacity(0.3)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.info_outline,
|
|
||||||
color: Colors.blue, size: 18),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Informasi',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
|
||||||
'Total stok akan dihitung otomatis dari jumlah penitipan bantuan yang telah terverifikasi.',
|
|
||||||
style: TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('Batal'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (formKey.currentState!.validate()) {
|
|
||||||
final stok = StokBantuanModel(
|
|
||||||
nama: namaController.text,
|
|
||||||
satuan: satuanController.text,
|
|
||||||
deskripsi: deskripsiController.text,
|
|
||||||
kategoriBantuanId: selectedJenisBantuanId,
|
|
||||||
isUang: isUang,
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
updatedAt: DateTime.now(),
|
|
||||||
);
|
|
||||||
controller.addStok(stok);
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('Simpan'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showEditStokDialog(BuildContext context, StokBantuanModel stok) {
|
|
||||||
final formKey = GlobalKey<FormState>();
|
|
||||||
final namaController = TextEditingController(text: stok.nama);
|
|
||||||
final satuanController = TextEditingController(text: stok.satuan);
|
|
||||||
final deskripsiController = TextEditingController(text: stok.deskripsi);
|
|
||||||
String? selectedJenisBantuanId = stok.kategoriBantuanId;
|
|
||||||
bool isUang = stok.isUang ?? false;
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => StatefulBuilder(
|
|
||||||
builder: (context, setState) => AlertDialog(
|
|
||||||
title: const Text('Edit Stok Bantuan'),
|
|
||||||
content: Form(
|
|
||||||
key: formKey,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: namaController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Nama Bantuan',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Nama bantuan tidak boleh kosong';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
DropdownButtonFormField<String>(
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Kategori Bantuan',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
value: selectedJenisBantuanId,
|
|
||||||
hint: const Text('Pilih Kategori Bantuan'),
|
|
||||||
isExpanded: true,
|
|
||||||
items: controller.daftarKategoriBantuan
|
|
||||||
.map((kategori) => DropdownMenuItem<String>(
|
|
||||||
value: kategori['id'],
|
|
||||||
child: Text(
|
|
||||||
kategori['nama'] ?? '',
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
selectedJenisBantuanId = value;
|
|
||||||
},
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'Kategori bantuan harus dipilih';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Tambahkan checkbox untuk menandai sebagai uang
|
|
||||||
CheckboxListTile(
|
|
||||||
title: const Text('Bantuan Berbentuk Uang (Rupiah)'),
|
|
||||||
value: isUang,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
isUang = value ?? false;
|
|
||||||
if (isUang) {
|
|
||||||
satuanController.text = 'Rp';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Tampilkan total stok saat ini (read-only)
|
|
||||||
InputDecorator(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: isUang
|
|
||||||
? 'Total Dana Saat Ini'
|
|
||||||
: 'Total Stok Saat Ini',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
contentPadding: EdgeInsets.all(10),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
isUang
|
|
||||||
? 'Rp ${DateFormatter.formatNumber(stok.totalStok)}'
|
|
||||||
: '${DateFormatter.formatNumber(stok.totalStok)} ${stok.satuan ?? ''}',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Hanya tampilkan input satuan
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: satuanController,
|
controller: satuanController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Satuan',
|
border: OutlineInputBorder(
|
||||||
border: OutlineInputBorder(),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
hintText: 'Contoh: Kg, Liter, Paket',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
),
|
),
|
||||||
enabled: !isUang, // Disable jika berbentuk uang
|
enabled: !isUang, // Disable jika berbentuk uang
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
@ -782,16 +636,28 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Deskripsi
|
||||||
|
Text(
|
||||||
|
'Deskripsi',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: deskripsiController,
|
controller: deskripsiController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Deskripsi',
|
border: OutlineInputBorder(
|
||||||
border: OutlineInputBorder(),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
hintText: 'Masukkan deskripsi bantuan',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
),
|
),
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Tambahkan informasi tentang total stok
|
|
||||||
|
// Informasi
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -826,15 +692,288 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Tombol aksi
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('Batal'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (formKey.currentState!.validate()) {
|
||||||
|
final stok = StokBantuanModel(
|
||||||
|
nama: namaController.text,
|
||||||
|
satuan: satuanController.text,
|
||||||
|
deskripsi: deskripsiController.text,
|
||||||
|
kategoriBantuanId: selectedJenisBantuanId,
|
||||||
|
isUang: isUang,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
controller.addStok(stok);
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
child: const Text('Simpan'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
barrierDismissible: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEditStokDialog(BuildContext context, StokBantuanModel stok) {
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final namaController = TextEditingController(text: stok.nama);
|
||||||
|
final satuanController = TextEditingController(text: stok.satuan);
|
||||||
|
final deskripsiController = TextEditingController(text: stok.deskripsi);
|
||||||
|
String? selectedJenisBantuanId = stok.kategoriBantuanId;
|
||||||
|
bool isUang = stok.isUang ?? false;
|
||||||
|
|
||||||
|
Get.dialog(
|
||||||
|
Dialog(
|
||||||
|
insetPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: StatefulBuilder(
|
||||||
|
builder: (context, setState) => Form(
|
||||||
|
key: formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Edit Stok Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Nama Bantuan
|
||||||
|
Text(
|
||||||
|
'Nama Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: namaController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
hintText: 'Masukkan nama bantuan',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Nama bantuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Kategori Bantuan
|
||||||
|
Text(
|
||||||
|
'Kategori Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
hint: const Text('Pilih kategori bantuan'),
|
||||||
|
value: selectedJenisBantuanId,
|
||||||
|
isExpanded: true,
|
||||||
|
items: controller.daftarKategoriBantuan
|
||||||
|
.map((kategori) => DropdownMenuItem<String>(
|
||||||
|
value: kategori['id'],
|
||||||
|
child: Text(
|
||||||
|
kategori['nama'] ?? '',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedJenisBantuanId = value;
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Kategori bantuan harus dipilih';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Checkbox untuk bantuan berbentuk uang
|
||||||
|
CheckboxListTile(
|
||||||
|
title: const Text('Bantuan Berbentuk Uang (Rupiah)'),
|
||||||
|
value: isUang,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
isUang = value ?? false;
|
||||||
|
if (isUang) {
|
||||||
|
satuanController.text = 'Rp';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
activeColor: AppTheme.primaryColor,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Total Stok Saat Ini
|
||||||
|
Text(
|
||||||
|
isUang ? 'Total Dana Saat Ini' : 'Total Stok Saat Ini',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
isUang ? Icons.monetization_on : Icons.inventory_2,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
isUang
|
||||||
|
? 'Rp ${DateFormatter.formatNumber(stok.totalStok)}'
|
||||||
|
: '${DateFormatter.formatNumber(stok.totalStok)} ${stok.satuan ?? ''}',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Satuan
|
||||||
|
Text(
|
||||||
|
'Satuan',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: satuanController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
hintText: 'Contoh: Kg, Liter, Paket',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
enabled: !isUang, // Disable jika berbentuk uang
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Satuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Deskripsi
|
||||||
|
Text(
|
||||||
|
'Deskripsi',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: deskripsiController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
hintText: 'Masukkan deskripsi bantuan',
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 8),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Informasi
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.blue.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline,
|
||||||
|
color: Colors.blue, size: 18),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Informasi',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Total stok dihitung otomatis dari jumlah penitipan bantuan yang telah terverifikasi dan tidak dapat diubah secara manual.',
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Tombol aksi
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Get.back(),
|
||||||
child: const Text('Batal'),
|
child: const Text('Batal'),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (formKey.currentState!.validate()) {
|
if (formKey.currentState!.validate()) {
|
||||||
@ -849,39 +988,166 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
controller.updateStok(updatedStok);
|
controller.updateStok(updatedStok);
|
||||||
Navigator.pop(context);
|
Get.back();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
child: const Text('Simpan'),
|
child: const Text('Simpan'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDeleteConfirmation(BuildContext context, StokBantuanModel stok) {
|
void _showDeleteConfirmation(BuildContext context, StokBantuanModel stok) {
|
||||||
showDialog(
|
Get.dialog(
|
||||||
context: context,
|
Dialog(
|
||||||
builder: (context) => AlertDialog(
|
insetPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
title: const Text('Konfirmasi Hapus'),
|
child: Padding(
|
||||||
content: Text(
|
padding: const EdgeInsets.all(16.0),
|
||||||
'Apakah Anda yakin ingin menghapus stok bantuan "${stok.nama}"?'),
|
child: Column(
|
||||||
actions: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Konfirmasi Hapus',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('Apakah Anda yakin ingin menghapus stok bantuan berikut?'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.grey.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
stok.nama ?? 'Tanpa Nama',
|
||||||
|
style:
|
||||||
|
TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
|
),
|
||||||
|
if (stok.deskripsi != null &&
|
||||||
|
stok.deskripsi!.isNotEmpty) ...[
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
stok.deskripsi!,
|
||||||
|
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
stok.isUang == true
|
||||||
|
? Icons.monetization_on
|
||||||
|
: Icons.inventory,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
stok.isUang == true
|
||||||
|
? 'Rp ${DateFormatter.formatNumber(stok.totalStok)}'
|
||||||
|
: '${DateFormatter.formatNumber(stok.totalStok)} ${stok.satuan ?? ''}',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.red.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.warning_amber_rounded,
|
||||||
|
color: Colors.red, size: 20),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Perhatian: Tindakan ini tidak dapat dibatalkan!',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red, fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Get.back(),
|
||||||
child: const Text('Batal'),
|
child: const Text('Batal'),
|
||||||
),
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
controller.deleteStok(stok.id ?? '');
|
controller.deleteStok(stok.id ?? '');
|
||||||
Navigator.pop(context);
|
Get.back();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
child: const Text('Hapus'),
|
child: const Text('Hapus'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tambahkan widget untuk menampilkan waktu terakhir update
|
||||||
|
Widget _buildLastUpdateInfo(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final lastUpdate = controller.lastUpdateTime.value;
|
||||||
|
final formattedDate = DateFormatter.formatDateTimeWithHour(lastUpdate);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.update, size: 16, color: Colors.grey[600]),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'Data terupdate: $formattedDate',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,4 +53,8 @@ class DateFormatter {
|
|||||||
return number.toString(); // Fallback to basic format
|
return number.toString(); // Fallback to basic format
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String formatDateTimeWithHour(DateTime dateTime) {
|
||||||
|
return DateFormat('dd MMMM yyyy HH:mm', 'id_ID').format(dateTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
import 'package:penyaluran_app/app/services/auth_service.dart';
|
import 'package:penyaluran_app/app/services/auth_service.dart';
|
||||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
@ -10,6 +11,9 @@ import 'package:intl/date_symbol_data_local.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Inisialisasi GetStorage
|
||||||
|
await GetStorage.init();
|
||||||
|
|
||||||
// Inisialisasi data locale untuk format tanggal
|
// Inisialisasi data locale untuk format tanggal
|
||||||
await initializeDateFormatting('id_ID', null);
|
await initializeDateFormatting('id_ID', null);
|
||||||
|
|
||||||
|
@ -296,6 +296,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.2"
|
version: "4.7.2"
|
||||||
|
get_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: get_storage
|
||||||
|
sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -43,6 +43,7 @@ dependencies:
|
|||||||
|
|
||||||
# Untuk menyimpan data lokal
|
# Untuk menyimpan data lokal
|
||||||
shared_preferences: ^2.2.2
|
shared_preferences: ^2.2.2
|
||||||
|
get_storage: ^2.1.1
|
||||||
|
|
||||||
# Untuk validasi form
|
# Untuk validasi form
|
||||||
form_validator: ^2.1.1
|
form_validator: ^2.1.1
|
||||||
|
Reference in New Issue
Block a user