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:
Khafidh Fuadi
2025-03-11 22:14:07 +07:00
parent cdbd659d63
commit f7397cb9cf
12 changed files with 596 additions and 408 deletions

View File

@ -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,
);
}
}

View File

@ -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),

View File

@ -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,
),

View File

@ -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;
}
}

View File

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

View File

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

View File

@ -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,185 +456,192 @@ 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(
title: const Text('Tambah 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: 'Jenis 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'] ?? ''),
))
.toList(),
onChanged: (value) {
selectedJenisBantuanId = value;
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jenis bantuan harus dipilih';
}
return null;
},
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: jumlahController,
decoration: const InputDecoration(
labelText: 'Jumlah',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jumlah tidak boleh kosong';
}
if (double.tryParse(value) == null) {
return 'Jumlah harus berupa angka';
}
return null;
},
),
),
const SizedBox(width: 8),
Expanded(
flex: 1,
child: TextFormField(
controller: satuanController,
decoration: const InputDecoration(
labelText: 'Satuan',
border: OutlineInputBorder(),
),
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),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalMasuk ?? DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
if (picked != null) {
tanggalMasuk = picked;
}
},
child: InputDecorator(
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: const Text('Tambah Stok Bantuan'),
content: Form(
key: formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: namaController,
decoration: const InputDecoration(
labelText: 'Tanggal Masuk',
labelText: 'Nama Bantuan',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalMasuk),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Nama bantuan tidak boleh kosong';
}
return null;
},
),
),
const SizedBox(height: 16),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalKadaluarsa ??
DateTime.now().add(const Duration(days: 365)),
firstDate: DateTime.now(),
lastDate: DateTime(2030),
);
if (picked != null) {
tanggalKadaluarsa = picked;
}
},
child: InputDecorator(
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Tanggal Kadaluarsa',
labelText: 'Kategori Bantuan',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalKadaluarsa),
value: selectedJenisBantuanId,
hint: const Text('Pilih Kategori Bantuan'),
items: controller.daftarKategoriBantuan
.map((kategori) => DropdownMenuItem<String>(
value: kategori['id'],
child: Text(kategori['nama'] ?? ''),
))
.toList(),
onChanged: (value) {
selectedJenisBantuanId = value;
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Kategori bantuan harus dipilih';
}
return null;
},
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: jumlahController,
decoration: const InputDecoration(
labelText: 'Jumlah',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jumlah tidak boleh kosong';
}
if (double.tryParse(value) == null) {
return 'Jumlah harus berupa angka';
}
return null;
},
),
),
const SizedBox(width: 8),
Expanded(
flex: 1,
child: TextFormField(
controller: satuanController,
decoration: const InputDecoration(
labelText: 'Satuan',
border: OutlineInputBorder(),
),
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),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalMasuk,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalMasuk = picked;
});
}
},
child: InputDecorator(
decoration: const InputDecoration(
labelText: 'Tanggal Masuk',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDateTime(tanggalMasuk),
),
),
),
),
],
const SizedBox(height: 16),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalKadaluarsa ??
DateTime.now().add(const Duration(days: 365)),
firstDate: DateTime.now(),
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalKadaluarsa = picked;
});
}
},
child: InputDecorator(
decoration: const InputDecoration(
labelText: 'Tanggal Kadaluarsa',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalKadaluarsa),
),
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Batal'),
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
final stok = StokBantuanModel(
nama: namaController.text,
jumlah: double.parse(jumlahController.text),
satuan: satuanController.text,
deskripsi: deskripsiController.text,
kategoriBantuanId: selectedJenisBantuanId,
tanggalMasuk: tanggalMasuk,
tanggalKadaluarsa: tanggalKadaluarsa,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
controller.addStok(stok);
Navigator.pop(context);
}
},
child: const Text('Simpan'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Batal'),
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
final stok = StokBantuanModel(
nama: namaController.text,
jumlah: double.parse(jumlahController.text),
satuan: satuanController.text,
deskripsi: deskripsiController.text,
jenisBantuanId: selectedJenisBantuanId,
tanggalMasuk: tanggalMasuk,
tanggalKadaluarsa: tanggalKadaluarsa,
status: 'TERSEDIA',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
controller.addStok(stok);
Navigator.pop(context);
}
},
child: const Text('Simpan'),
),
],
),
);
}
@ -609,187 +653,199 @@ 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(
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: 'Jenis 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'] ?? ''),
))
.toList(),
onChanged: (value) {
selectedJenisBantuanId = value;
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jenis bantuan harus dipilih';
}
return null;
},
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: jumlahController,
decoration: const InputDecoration(
labelText: 'Jumlah',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jumlah tidak boleh kosong';
}
if (double.tryParse(value) == null) {
return 'Jumlah harus berupa angka';
}
return null;
},
),
),
const SizedBox(width: 8),
Expanded(
flex: 1,
child: TextFormField(
controller: satuanController,
decoration: const InputDecoration(
labelText: 'Satuan',
border: OutlineInputBorder(),
),
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),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalMasuk ?? DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
if (picked != null) {
tanggalMasuk = picked;
}
},
child: InputDecorator(
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: 'Tanggal Masuk',
labelText: 'Nama Bantuan',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalMasuk),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Nama bantuan tidak boleh kosong';
}
return null;
},
),
),
const SizedBox(height: 16),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalKadaluarsa ??
DateTime.now().add(const Duration(days: 365)),
firstDate: DateTime.now(),
lastDate: DateTime(2030),
);
if (picked != null) {
tanggalKadaluarsa = picked;
}
},
child: InputDecorator(
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Tanggal Kadaluarsa',
labelText: 'Kategori Bantuan',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalKadaluarsa),
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),
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: jumlahController,
decoration: const InputDecoration(
labelText: 'Jumlah',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jumlah tidak boleh kosong';
}
if (double.tryParse(value) == null) {
return 'Jumlah harus berupa angka';
}
return null;
},
),
),
const SizedBox(width: 8),
Expanded(
flex: 1,
child: TextFormField(
controller: satuanController,
decoration: const InputDecoration(
labelText: 'Satuan',
border: OutlineInputBorder(),
),
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),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalMasuk ?? DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalMasuk = picked;
});
}
},
child: InputDecorator(
decoration: const InputDecoration(
labelText: 'Tanggal Masuk',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDateTime(tanggalMasuk),
),
),
),
),
],
const SizedBox(height: 16),
InkWell(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalKadaluarsa ??
DateTime.now().add(const Duration(days: 365)),
firstDate: DateTime.now(),
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalKadaluarsa = picked;
});
}
},
child: InputDecorator(
decoration: const InputDecoration(
labelText: 'Tanggal Kadaluarsa',
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalKadaluarsa),
),
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Batal'),
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
final updatedStok = StokBantuanModel(
id: stok.id,
nama: namaController.text,
jumlah: double.parse(jumlahController.text),
satuan: satuanController.text,
deskripsi: deskripsiController.text,
kategoriBantuanId: selectedJenisBantuanId,
tanggalMasuk: tanggalMasuk,
tanggalKadaluarsa: tanggalKadaluarsa,
createdAt: stok.createdAt,
updatedAt: DateTime.now(),
);
controller.updateStok(updatedStok);
Navigator.pop(context);
}
},
child: const Text('Simpan'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Batal'),
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
final updatedStok = StokBantuanModel(
id: stok.id,
nama: namaController.text,
jumlah: double.parse(jumlahController.text),
satuan: satuanController.text,
deskripsi: deskripsiController.text,
jenisBantuanId: selectedJenisBantuanId,
tanggalMasuk: tanggalMasuk,
tanggalKadaluarsa: tanggalKadaluarsa,
status: stok.status,
createdAt: stok.createdAt,
updatedAt: DateTime.now(),
);
controller.updateStok(updatedStok);
Navigator.pop(context);
}
},
child: const Text('Simpan'),
),
],
),
);
}