Refactor stok bantuan model dan kontroller untuk mendukung kategori bantuan
- Ubah model StokBantuanModel dari 'jenis bantuan' menjadi 'kategori bantuan' - Perbarui metode loadJenisBantuanData() menjadi loadKategoriBantuanData() - Tambahkan metode baru untuk menghitung stok hampir habis dan segera kadaluarsa - Update tampilan dan form untuk menggunakan kategori bantuan - Perbaiki logika navigasi dan binding pada berbagai modul terkait
This commit is contained in:
@ -3,14 +3,11 @@ import 'dart:convert';
|
||||
class StokBantuanModel {
|
||||
final String? id;
|
||||
final String? nama;
|
||||
final String? bentukBantuanId;
|
||||
final String? sumberBantuanId;
|
||||
final String? jenisBantuanId;
|
||||
final Map<String, dynamic>? jenisBantuan;
|
||||
final String? kategoriBantuanId;
|
||||
final Map<String, dynamic>? kategoriBantuan;
|
||||
final double? jumlah;
|
||||
final String? satuan;
|
||||
final String? deskripsi;
|
||||
final String? status;
|
||||
final DateTime? tanggalMasuk;
|
||||
final DateTime? tanggalKadaluarsa;
|
||||
final DateTime? createdAt;
|
||||
@ -19,14 +16,11 @@ class StokBantuanModel {
|
||||
StokBantuanModel({
|
||||
this.id,
|
||||
this.nama,
|
||||
this.bentukBantuanId,
|
||||
this.sumberBantuanId,
|
||||
this.jenisBantuanId,
|
||||
this.jenisBantuan,
|
||||
this.kategoriBantuanId,
|
||||
this.kategoriBantuan,
|
||||
this.jumlah,
|
||||
this.satuan,
|
||||
this.deskripsi,
|
||||
this.status,
|
||||
this.tanggalMasuk,
|
||||
this.tanggalKadaluarsa,
|
||||
this.createdAt,
|
||||
@ -42,14 +36,11 @@ class StokBantuanModel {
|
||||
StokBantuanModel(
|
||||
id: json["id"],
|
||||
nama: json["nama"],
|
||||
bentukBantuanId: json["bentuk_bantuan_id"],
|
||||
sumberBantuanId: json["sumber_bantuan_id"],
|
||||
jenisBantuanId: json["jenis_bantuan_id"],
|
||||
jenisBantuan: json["jenis_bantuan"],
|
||||
kategoriBantuanId: json["kategori_bantuan_id"],
|
||||
kategoriBantuan: json["kategori_bantuan"],
|
||||
jumlah: json["jumlah"] != null ? json["jumlah"].toDouble() : 0.0,
|
||||
satuan: json["satuan"],
|
||||
deskripsi: json["deskripsi"],
|
||||
status: json["status"],
|
||||
tanggalMasuk: json["tanggal_masuk"] != null
|
||||
? DateTime.parse(json["tanggal_masuk"])
|
||||
: null,
|
||||
@ -64,19 +55,24 @@ class StokBantuanModel {
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = {
|
||||
"nama": nama,
|
||||
"bentuk_bantuan_id": bentukBantuanId,
|
||||
"sumber_bantuan_id": sumberBantuanId,
|
||||
"jenis_bantuan_id": jenisBantuanId,
|
||||
"kategori_bantuan_id": kategoriBantuanId,
|
||||
"jumlah": jumlah,
|
||||
"satuan": satuan,
|
||||
"deskripsi": deskripsi,
|
||||
"status": status,
|
||||
"tanggal_masuk": tanggalMasuk?.toIso8601String(),
|
||||
"tanggal_kadaluarsa": tanggalKadaluarsa?.toIso8601String(),
|
||||
"created_at": createdAt?.toIso8601String(),
|
||||
"updated_at": updatedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
// Tambahkan id hanya jika tidak null
|
||||
if (id != null) {
|
||||
data["id"] = id;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -116,9 +116,14 @@ class AuthController extends GetxController {
|
||||
final targetRoute = _getTargetRouteForRole(role);
|
||||
print('Target rute: $targetRoute');
|
||||
|
||||
if (currentRoute != targetRoute) {
|
||||
// Jika berada di splash atau login, navigasi ke dashboard
|
||||
if (currentRoute == Routes.splash || currentRoute == Routes.login) {
|
||||
print('Navigasi ke rute target berdasarkan role');
|
||||
navigateBasedOnRole(role);
|
||||
} else if (currentRoute != targetRoute) {
|
||||
// Jika berada di rute lain yang tidak sesuai dengan role, navigasi ke dashboard
|
||||
print('Berada di rute yang tidak sesuai, navigasi ke rute target');
|
||||
navigateBasedOnRole(role);
|
||||
} else {
|
||||
print('Sudah berada di rute yang sesuai, tidak perlu navigasi');
|
||||
}
|
||||
@ -334,7 +339,7 @@ class AuthController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
// Mendapatkan rute target berdasarkan peran
|
||||
// Mendapatkan rute target berdasarkan role
|
||||
String _getTargetRouteForRole(String role) {
|
||||
switch (role) {
|
||||
case 'WARGA':
|
||||
|
@ -17,45 +17,54 @@ class PetugasDesaBinding extends Bindings {
|
||||
Get.put(AuthController(), permanent: true);
|
||||
}
|
||||
|
||||
// Main controller
|
||||
Get.lazyPut<PetugasDesaController>(
|
||||
() => PetugasDesaController(),
|
||||
fenix: true,
|
||||
);
|
||||
// Main controller - gunakan put dengan permanent untuk controller utama
|
||||
if (!Get.isRegistered<PetugasDesaController>()) {
|
||||
Get.put(PetugasDesaController(), permanent: true);
|
||||
} else {
|
||||
// Jika sudah terdaftar, gunakan find untuk mendapatkan instance yang ada
|
||||
Get.find<PetugasDesaController>();
|
||||
}
|
||||
|
||||
// Dashboard controller
|
||||
Get.lazyPut<PetugasDesaDashboardController>(
|
||||
() => PetugasDesaDashboardController(),
|
||||
fenix: true,
|
||||
);
|
||||
|
||||
// Jadwal penyaluran controller
|
||||
Get.lazyPut<JadwalPenyaluranController>(
|
||||
() => JadwalPenyaluranController(),
|
||||
fenix: true,
|
||||
);
|
||||
|
||||
// Stok bantuan controller
|
||||
Get.lazyPut<StokBantuanController>(
|
||||
() => StokBantuanController(),
|
||||
fenix: true,
|
||||
);
|
||||
|
||||
// Penitipan bantuan controller
|
||||
Get.lazyPut<PenitipanBantuanController>(
|
||||
() => PenitipanBantuanController(),
|
||||
fenix: true,
|
||||
);
|
||||
|
||||
// Pengaduan controller
|
||||
Get.lazyPut<PengaduanController>(
|
||||
() => PengaduanController(),
|
||||
fenix: true,
|
||||
);
|
||||
|
||||
// Penerima bantuan controller
|
||||
Get.lazyPut<PenerimaBantuanController>(
|
||||
() => PenerimaBantuanController(),
|
||||
fenix: true,
|
||||
);
|
||||
|
||||
// Laporan controller
|
||||
Get.lazyPut<LaporanController>(
|
||||
() => LaporanController(),
|
||||
fenix: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ class JadwalSectionWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Jenis Bantuan: ${jadwalData['jenis_bantuan'] ?? ''}',
|
||||
'Kategori Bantuan: ${jadwalData['kategori_bantuan'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
@ -181,7 +181,7 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Jenis: ${permintaanData['jenis_bantuan'] ?? ''}',
|
||||
'Kategori: ${permintaanData['kategori_bantuan'] ?? ''}',
|
||||
style: textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
@ -17,8 +17,8 @@ class StokBantuanController extends GetxController {
|
||||
final RxDouble stokMasuk = 0.0.obs;
|
||||
final RxDouble stokKeluar = 0.0.obs;
|
||||
|
||||
// Data untuk jenis bantuan
|
||||
final RxList<Map<String, dynamic>> daftarJenisBantuan =
|
||||
// Data untuk kategori bantuan
|
||||
final RxList<Map<String, dynamic>> daftarKategoriBantuan =
|
||||
<Map<String, dynamic>>[].obs;
|
||||
|
||||
// Controller untuk pencarian
|
||||
@ -31,7 +31,7 @@ class StokBantuanController extends GetxController {
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
loadStokBantuanData();
|
||||
loadJenisBantuanData();
|
||||
loadKategoriBantuanData();
|
||||
|
||||
// Listener untuk pencarian
|
||||
searchController.addListener(() {
|
||||
@ -74,14 +74,14 @@ class StokBantuanController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadJenisBantuanData() async {
|
||||
Future<void> loadKategoriBantuanData() async {
|
||||
try {
|
||||
final jenisBantuanData = await _supabaseService.getJenisBantuan();
|
||||
if (jenisBantuanData != null) {
|
||||
daftarJenisBantuan.value = jenisBantuanData;
|
||||
final kategoriBantuanData = await _supabaseService.getKategoriBantuan();
|
||||
if (kategoriBantuanData != null) {
|
||||
daftarKategoriBantuan.value = kategoriBantuanData;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading jenis bantuan data: $e');
|
||||
print('Error loading kategori bantuan data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ class StokBantuanController extends GetxController {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
await loadStokBantuanData();
|
||||
await loadJenisBantuanData();
|
||||
await loadKategoriBantuanData();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@ -194,4 +194,18 @@ class StokBantuanController extends GetxController {
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
// Metode untuk mendapatkan jumlah stok yang hampir habis (stok <= 10)
|
||||
int getStokHampirHabis() {
|
||||
return daftarStokBantuan.where((stok) => (stok.jumlah ?? 0) <= 10).length;
|
||||
}
|
||||
|
||||
// Metode untuk mendapatkan jumlah stok yang segera kadaluarsa (dalam 30 hari)
|
||||
int getStokSegeraKadaluarsa() {
|
||||
return daftarStokBantuan
|
||||
.where((stok) =>
|
||||
stok.tanggalKadaluarsa != null &&
|
||||
stok.tanggalKadaluarsa!.difference(DateTime.now()).inDays <= 30)
|
||||
.length;
|
||||
}
|
||||
}
|
||||
|
@ -54,8 +54,8 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoItem(context,
|
||||
icon: Icons.category,
|
||||
label: 'Jenis Bantuan',
|
||||
value: jadwal['jenis_bantuan'] ?? '-'),
|
||||
label: 'Kategori Bantuan',
|
||||
value: jadwal['kategori_bantuan'] ?? '-'),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoItem(context,
|
||||
icon: Icons.calendar_today,
|
||||
|
@ -169,7 +169,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
||||
{
|
||||
'id': '1',
|
||||
'donatur': 'PT Sejahtera Abadi',
|
||||
'jenis_bantuan': 'Sembako',
|
||||
'kategori_bantuan': 'Sembako',
|
||||
'jumlah': '500 kg',
|
||||
'tanggal_pengajuan': '15 April 2023',
|
||||
'status': 'Menunggu',
|
||||
@ -177,7 +177,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
||||
{
|
||||
'id': '2',
|
||||
'donatur': 'Yayasan Peduli Sesama',
|
||||
'jenis_bantuan': 'Pakaian',
|
||||
'kategori_bantuan': 'Pakaian',
|
||||
'jumlah': '200 pcs',
|
||||
'tanggal_pengajuan': '14 April 2023',
|
||||
'status': 'Terverifikasi',
|
||||
@ -185,7 +185,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
||||
{
|
||||
'id': '3',
|
||||
'donatur': 'Bank BRI',
|
||||
'jenis_bantuan': 'Beras',
|
||||
'kategori_bantuan': 'Beras',
|
||||
'jumlah': '300 kg',
|
||||
'tanggal_pengajuan': '13 April 2023',
|
||||
'status': 'Terverifikasi',
|
||||
@ -193,7 +193,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
||||
{
|
||||
'id': '4',
|
||||
'donatur': 'Komunitas Peduli',
|
||||
'jenis_bantuan': 'Alat Tulis',
|
||||
'kategori_bantuan': 'Alat Tulis',
|
||||
'jumlah': '100 set',
|
||||
'tanggal_pengajuan': '12 April 2023',
|
||||
'status': 'Ditolak',
|
||||
@ -304,8 +304,8 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.category,
|
||||
label: 'Jenis Bantuan',
|
||||
value: item['jenis_bantuan'] ?? '',
|
||||
label: 'Kategori Bantuan',
|
||||
value: item['kategori_bantuan'] ?? '',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
@ -23,7 +23,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
_showAddStokDialog(context);
|
||||
},
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
child: const Icon(Icons.add),
|
||||
child: const Icon(Icons.add, color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -79,7 +79,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.inventory_2_outlined,
|
||||
title: 'Total Stok',
|
||||
title: 'Stok Tersedia',
|
||||
value: DateFormatter.formatNumber(controller.totalStok.value),
|
||||
),
|
||||
),
|
||||
@ -87,7 +87,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.input,
|
||||
title: 'Masuk',
|
||||
title: 'Total Masuk',
|
||||
value: DateFormatter.formatNumber(controller.stokMasuk.value),
|
||||
),
|
||||
),
|
||||
@ -95,13 +95,48 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.output,
|
||||
title: 'Keluar',
|
||||
title: 'Total Keluar',
|
||||
value:
|
||||
DateFormatter.formatNumber(controller.stokKeluar.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.warning_amber_rounded,
|
||||
title: 'Hampir Habis',
|
||||
value: '${controller.getStokHampirHabis()}',
|
||||
valueColor: controller.getStokHampirHabis() > 0
|
||||
? Colors.amber
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.access_time,
|
||||
title: 'Segera Kadaluarsa',
|
||||
value: '${controller.getStokSegeraKadaluarsa()}',
|
||||
valueColor: controller.getStokSegeraKadaluarsa() > 0
|
||||
? Colors.amber
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.category_outlined,
|
||||
title: 'Kategori Bantuan',
|
||||
value: '${controller.daftarKategoriBantuan.length}',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -112,6 +147,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String value,
|
||||
Color? valueColor,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
@ -132,7 +168,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
color: valueColor ?? Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@ -276,9 +312,10 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
item.jenisBantuan != null
|
||||
? (item.jenisBantuan!['nama'] ?? 'Tidak Ada Jenis')
|
||||
: 'Tidak Ada Jenis',
|
||||
item.kategoriBantuan != null
|
||||
? (item.kategoriBantuan!['nama'] ??
|
||||
'Tidak Ada Kategori')
|
||||
: 'Tidak Ada Kategori',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -314,7 +351,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
context,
|
||||
icon: Icons.calendar_today,
|
||||
label: 'Tanggal Masuk',
|
||||
value: DateFormatter.formatDate(item.tanggalMasuk),
|
||||
value: DateFormatter.formatDateTime(item.tanggalMasuk),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -335,7 +372,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
context,
|
||||
icon: Icons.access_time,
|
||||
label: 'Terakhir Diperbarui',
|
||||
value: DateFormatter.formatDate(item.updatedAt),
|
||||
value: DateFormatter.formatDateTime(item.updatedAt),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -419,12 +456,15 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
final satuanController = TextEditingController();
|
||||
final deskripsiController = TextEditingController();
|
||||
String? selectedJenisBantuanId;
|
||||
DateTime? tanggalMasuk = DateTime.now();
|
||||
|
||||
// Gunakan StatefulBuilder untuk memperbarui state dialog
|
||||
DateTime tanggalMasuk = DateTime.now();
|
||||
DateTime? tanggalKadaluarsa;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setState) => AlertDialog(
|
||||
title: const Text('Tambah Stok Bantuan'),
|
||||
content: Form(
|
||||
key: formKey,
|
||||
@ -448,15 +488,15 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<String>(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Jenis Bantuan',
|
||||
labelText: 'Kategori Bantuan',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
value: selectedJenisBantuanId,
|
||||
hint: const Text('Pilih Jenis Bantuan'),
|
||||
items: controller.daftarJenisBantuan
|
||||
.map((jenis) => DropdownMenuItem<String>(
|
||||
value: jenis['id'],
|
||||
child: Text(jenis['nama'] ?? ''),
|
||||
hint: const Text('Pilih Kategori Bantuan'),
|
||||
items: controller.daftarKategoriBantuan
|
||||
.map((kategori) => DropdownMenuItem<String>(
|
||||
value: kategori['id'],
|
||||
child: Text(kategori['nama'] ?? ''),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
@ -464,7 +504,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Jenis bantuan harus dipilih';
|
||||
return 'Kategori bantuan harus dipilih';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@ -525,12 +565,14 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
onTap: () async {
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: tanggalMasuk ?? DateTime.now(),
|
||||
initialDate: tanggalMasuk,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2030),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
tanggalMasuk = picked;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: InputDecorator(
|
||||
@ -539,7 +581,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
child: Text(
|
||||
DateFormatter.formatDate(tanggalMasuk),
|
||||
DateFormatter.formatDateTime(tanggalMasuk),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -554,7 +596,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
lastDate: DateTime(2030),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
tanggalKadaluarsa = picked;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: InputDecorator(
|
||||
@ -584,10 +628,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
jumlah: double.parse(jumlahController.text),
|
||||
satuan: satuanController.text,
|
||||
deskripsi: deskripsiController.text,
|
||||
jenisBantuanId: selectedJenisBantuanId,
|
||||
kategoriBantuanId: selectedJenisBantuanId,
|
||||
tanggalMasuk: tanggalMasuk,
|
||||
tanggalKadaluarsa: tanggalKadaluarsa,
|
||||
status: 'TERSEDIA',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
@ -599,6 +642,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -609,13 +653,16 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
TextEditingController(text: stok.jumlah?.toString());
|
||||
final satuanController = TextEditingController(text: stok.satuan);
|
||||
final deskripsiController = TextEditingController(text: stok.deskripsi);
|
||||
String? selectedJenisBantuanId = stok.jenisBantuanId;
|
||||
String? selectedJenisBantuanId = stok.kategoriBantuanId;
|
||||
|
||||
// Gunakan StatefulBuilder untuk memperbarui state dialog
|
||||
DateTime? tanggalMasuk = stok.tanggalMasuk;
|
||||
DateTime? tanggalKadaluarsa = stok.tanggalKadaluarsa;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setState) => AlertDialog(
|
||||
title: const Text('Edit Stok Bantuan'),
|
||||
content: Form(
|
||||
key: formKey,
|
||||
@ -639,15 +686,20 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<String>(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Jenis Bantuan',
|
||||
labelText: 'Kategori Bantuan',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
value: selectedJenisBantuanId,
|
||||
hint: const Text('Pilih Jenis Bantuan'),
|
||||
items: controller.daftarJenisBantuan
|
||||
.map((jenis) => DropdownMenuItem<String>(
|
||||
value: jenis['id'],
|
||||
child: Text(jenis['nama'] ?? ''),
|
||||
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) {
|
||||
@ -655,7 +707,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Jenis bantuan harus dipilih';
|
||||
return 'Kategori bantuan harus dipilih';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@ -721,7 +773,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
lastDate: DateTime(2030),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
tanggalMasuk = picked;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: InputDecorator(
|
||||
@ -730,7 +784,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
child: Text(
|
||||
DateFormatter.formatDate(tanggalMasuk),
|
||||
DateFormatter.formatDateTime(tanggalMasuk),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -745,7 +799,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
lastDate: DateTime(2030),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
tanggalKadaluarsa = picked;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: InputDecorator(
|
||||
@ -776,10 +832,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
jumlah: double.parse(jumlahController.text),
|
||||
satuan: satuanController.text,
|
||||
deskripsi: deskripsiController.text,
|
||||
jenisBantuanId: selectedJenisBantuanId,
|
||||
kategoriBantuanId: selectedJenisBantuanId,
|
||||
tanggalMasuk: tanggalMasuk,
|
||||
tanggalKadaluarsa: tanggalKadaluarsa,
|
||||
status: stok.status,
|
||||
createdAt: stok.createdAt,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
@ -791,6 +846,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
@ -14,12 +15,19 @@ class _SplashViewState extends State<SplashView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_navigateToLogin();
|
||||
_checkAuthAndNavigate();
|
||||
}
|
||||
|
||||
_navigateToLogin() async {
|
||||
_checkAuthAndNavigate() async {
|
||||
// Tunggu 2 detik untuk menampilkan splash screen
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
Get.offAllNamed(Routes.login);
|
||||
|
||||
// Dapatkan AuthController dan periksa status autentikasi
|
||||
final AuthController authController = Get.find<AuthController>();
|
||||
await authController.checkAuthStatus();
|
||||
|
||||
// Navigasi akan ditangani oleh AuthController
|
||||
// Tidak perlu navigasi manual di sini
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -9,6 +9,9 @@ class SupabaseService extends GetxService {
|
||||
// Cache untuk profil pengguna
|
||||
Map<String, dynamic>? _cachedUserProfile;
|
||||
|
||||
// Flag untuk menandai apakah sesi sudah diinisialisasi
|
||||
bool _isSessionInitialized = false;
|
||||
|
||||
// Ganti dengan URL dan API key Supabase Anda
|
||||
static const String supabaseUrl = String.fromEnvironment('SUPABASE_URL',
|
||||
defaultValue: 'http://labulabs.net:8000');
|
||||
@ -17,13 +20,47 @@ class SupabaseService extends GetxService {
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzMxODYyODAwLAogICJleHAiOiAxODg5NjI5MjAwCn0.4IpwhwCVbfYXxb8JlZOLSBzCt6kQmypkvuso7N8Aicc');
|
||||
|
||||
Future<SupabaseService> init() async {
|
||||
try {
|
||||
await Supabase.initialize(
|
||||
url: supabaseUrl,
|
||||
anonKey: supabaseKey,
|
||||
debug: true, // Aktifkan debug untuk melihat log autentikasi
|
||||
);
|
||||
|
||||
client = Supabase.instance.client;
|
||||
|
||||
// Tambahkan listener untuk perubahan autentikasi
|
||||
client.auth.onAuthStateChange.listen((data) {
|
||||
final AuthChangeEvent event = data.event;
|
||||
print('DEBUG: Auth state changed: $event');
|
||||
|
||||
if (event == AuthChangeEvent.signedIn) {
|
||||
print('DEBUG: User signed in');
|
||||
_isSessionInitialized = true;
|
||||
} else if (event == AuthChangeEvent.signedOut) {
|
||||
print('DEBUG: User signed out');
|
||||
_cachedUserProfile = null;
|
||||
_isSessionInitialized = false;
|
||||
} else if (event == AuthChangeEvent.tokenRefreshed) {
|
||||
print('DEBUG: Token refreshed');
|
||||
_isSessionInitialized = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Periksa apakah ada sesi yang aktif
|
||||
final session = client.auth.currentSession;
|
||||
if (session != null) {
|
||||
print('DEBUG: Session aktif ditemukan saat inisialisasi');
|
||||
_isSessionInitialized = true;
|
||||
} else {
|
||||
print('DEBUG: Tidak ada session aktif saat inisialisasi');
|
||||
}
|
||||
|
||||
return this;
|
||||
} catch (e) {
|
||||
print('ERROR: Gagal inisialisasi Supabase: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Metode untuk mendaftar pengguna baru
|
||||
@ -37,23 +74,52 @@ class SupabaseService extends GetxService {
|
||||
|
||||
// Metode untuk login
|
||||
Future<AuthResponse> signIn(String email, String password) async {
|
||||
return await client.auth.signInWithPassword(
|
||||
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
|
||||
Future<void> signOut() async {
|
||||
_cachedUserProfile = null; // Hapus cache saat logout
|
||||
_isSessionInitialized = false;
|
||||
await client.auth.signOut();
|
||||
print('DEBUG: Logout berhasil, sesi dihapus');
|
||||
}
|
||||
|
||||
// Metode untuk mendapatkan user saat ini
|
||||
User? get currentUser => client.auth.currentUser;
|
||||
|
||||
// Metode untuk memeriksa apakah user sudah login
|
||||
bool get isAuthenticated => currentUser != null;
|
||||
bool get isAuthenticated {
|
||||
final user = currentUser;
|
||||
final session = client.auth.currentSession;
|
||||
|
||||
if (user != null && session != null) {
|
||||
// Periksa apakah token masih valid
|
||||
final now = DateTime.now().millisecondsSinceEpoch / 1000;
|
||||
final isValid = session.expiresAt != null && session.expiresAt! > now;
|
||||
|
||||
if (isValid) {
|
||||
print('DEBUG: Sesi valid, user terautentikasi');
|
||||
return true;
|
||||
} else {
|
||||
print('DEBUG: Sesi kedaluwarsa, user tidak terautentikasi');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
print('DEBUG: Tidak ada user atau sesi, user tidak terautentikasi');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Metode untuk mendapatkan profil pengguna
|
||||
Future<Map<String, dynamic>?> getUserProfile() async {
|
||||
@ -270,7 +336,7 @@ class SupabaseService extends GetxService {
|
||||
try {
|
||||
final response = await client
|
||||
.from('stok_bantuan')
|
||||
.select('*, jenis_bantuan:jenis_bantuan_id(id, nama)');
|
||||
.select('*, kategori_bantuan:kategori_bantuan_id(id, nama)');
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
@ -318,18 +384,23 @@ class SupabaseService extends GetxService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>?> getJenisBantuan() async {
|
||||
Future<List<Map<String, dynamic>>?> getKategoriBantuan() async {
|
||||
try {
|
||||
final response = await client.from('jenis_bantuan').select('*');
|
||||
final response = await client.from('kategori_bantuan').select('*');
|
||||
return response;
|
||||
} catch (e) {
|
||||
print('Error getting jenis bantuan: $e');
|
||||
print('Error getting kategori bantuan: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addStok(Map<String, dynamic> stokData) async {
|
||||
try {
|
||||
print('stokData: $stokData');
|
||||
// Hapus id dari stokData jika ada, biarkan Supabase yang menghasilkan id
|
||||
if (stokData.containsKey('id')) {
|
||||
stokData.remove('id');
|
||||
}
|
||||
await client.from('stok_bantuan').insert(stokData);
|
||||
} catch (e) {
|
||||
print('Error adding stok: $e');
|
||||
|
@ -14,6 +14,35 @@ class DateFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
static String formatTime(DateTime? time,
|
||||
{String format = 'HH:mm',
|
||||
String locale = 'id_ID',
|
||||
String defaultValue = '-'}) {
|
||||
if (time == null) return defaultValue;
|
||||
try {
|
||||
return DateFormat(format, locale).format(time);
|
||||
} catch (e) {
|
||||
print('Error formatting time: $e');
|
||||
return time
|
||||
.toString()
|
||||
.split(' ')[1]
|
||||
.substring(0, 5); // Fallback to basic format
|
||||
}
|
||||
}
|
||||
|
||||
static String formatDateTime(DateTime? dateTime,
|
||||
{String format = 'dd MMMM yyyy HH:mm',
|
||||
String locale = 'id_ID',
|
||||
String defaultValue = '-'}) {
|
||||
if (dateTime == null) return defaultValue;
|
||||
try {
|
||||
return DateFormat(format, locale).format(dateTime);
|
||||
} catch (e) {
|
||||
print('Error formatting date time: $e');
|
||||
return dateTime.toString().split('.')[0]; // Fallback to basic format
|
||||
}
|
||||
}
|
||||
|
||||
static String formatNumber(num? number,
|
||||
{String locale = 'id_ID', String defaultValue = '0'}) {
|
||||
if (number == null) return defaultValue;
|
||||
|
Reference in New Issue
Block a user