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 {
|
class StokBantuanModel {
|
||||||
final String? id;
|
final String? id;
|
||||||
final String? nama;
|
final String? nama;
|
||||||
final String? bentukBantuanId;
|
final String? kategoriBantuanId;
|
||||||
final String? sumberBantuanId;
|
final Map<String, dynamic>? kategoriBantuan;
|
||||||
final String? jenisBantuanId;
|
|
||||||
final Map<String, dynamic>? jenisBantuan;
|
|
||||||
final double? jumlah;
|
final double? jumlah;
|
||||||
final String? satuan;
|
final String? satuan;
|
||||||
final String? deskripsi;
|
final String? deskripsi;
|
||||||
final String? status;
|
|
||||||
final DateTime? tanggalMasuk;
|
final DateTime? tanggalMasuk;
|
||||||
final DateTime? tanggalKadaluarsa;
|
final DateTime? tanggalKadaluarsa;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
@ -19,14 +16,11 @@ class StokBantuanModel {
|
|||||||
StokBantuanModel({
|
StokBantuanModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.nama,
|
this.nama,
|
||||||
this.bentukBantuanId,
|
this.kategoriBantuanId,
|
||||||
this.sumberBantuanId,
|
this.kategoriBantuan,
|
||||||
this.jenisBantuanId,
|
|
||||||
this.jenisBantuan,
|
|
||||||
this.jumlah,
|
this.jumlah,
|
||||||
this.satuan,
|
this.satuan,
|
||||||
this.deskripsi,
|
this.deskripsi,
|
||||||
this.status,
|
|
||||||
this.tanggalMasuk,
|
this.tanggalMasuk,
|
||||||
this.tanggalKadaluarsa,
|
this.tanggalKadaluarsa,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
@ -42,14 +36,11 @@ class StokBantuanModel {
|
|||||||
StokBantuanModel(
|
StokBantuanModel(
|
||||||
id: json["id"],
|
id: json["id"],
|
||||||
nama: json["nama"],
|
nama: json["nama"],
|
||||||
bentukBantuanId: json["bentuk_bantuan_id"],
|
kategoriBantuanId: json["kategori_bantuan_id"],
|
||||||
sumberBantuanId: json["sumber_bantuan_id"],
|
kategoriBantuan: json["kategori_bantuan"],
|
||||||
jenisBantuanId: json["jenis_bantuan_id"],
|
|
||||||
jenisBantuan: json["jenis_bantuan"],
|
|
||||||
jumlah: json["jumlah"] != null ? json["jumlah"].toDouble() : 0.0,
|
jumlah: json["jumlah"] != null ? json["jumlah"].toDouble() : 0.0,
|
||||||
satuan: json["satuan"],
|
satuan: json["satuan"],
|
||||||
deskripsi: json["deskripsi"],
|
deskripsi: json["deskripsi"],
|
||||||
status: json["status"],
|
|
||||||
tanggalMasuk: json["tanggal_masuk"] != null
|
tanggalMasuk: json["tanggal_masuk"] != null
|
||||||
? DateTime.parse(json["tanggal_masuk"])
|
? DateTime.parse(json["tanggal_masuk"])
|
||||||
: null,
|
: null,
|
||||||
@ -64,19 +55,24 @@ class StokBantuanModel {
|
|||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() {
|
||||||
"id": id,
|
final Map<String, dynamic> data = {
|
||||||
"nama": nama,
|
"nama": nama,
|
||||||
"bentuk_bantuan_id": bentukBantuanId,
|
"kategori_bantuan_id": kategoriBantuanId,
|
||||||
"sumber_bantuan_id": sumberBantuanId,
|
"jumlah": jumlah,
|
||||||
"jenis_bantuan_id": jenisBantuanId,
|
"satuan": satuan,
|
||||||
"jumlah": jumlah,
|
"deskripsi": deskripsi,
|
||||||
"satuan": satuan,
|
"tanggal_masuk": tanggalMasuk?.toIso8601String(),
|
||||||
"deskripsi": deskripsi,
|
"tanggal_kadaluarsa": tanggalKadaluarsa?.toIso8601String(),
|
||||||
"status": status,
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"tanggal_masuk": tanggalMasuk?.toIso8601String(),
|
"updated_at": updatedAt?.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);
|
final targetRoute = _getTargetRouteForRole(role);
|
||||||
print('Target rute: $targetRoute');
|
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');
|
print('Navigasi ke rute target berdasarkan role');
|
||||||
navigateBasedOnRole(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 {
|
} else {
|
||||||
print('Sudah berada di rute yang sesuai, tidak perlu navigasi');
|
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) {
|
String _getTargetRouteForRole(String role) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case 'WARGA':
|
case 'WARGA':
|
||||||
|
@ -17,45 +17,54 @@ class PetugasDesaBinding extends Bindings {
|
|||||||
Get.put(AuthController(), permanent: true);
|
Get.put(AuthController(), permanent: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main controller
|
// Main controller - gunakan put dengan permanent untuk controller utama
|
||||||
Get.lazyPut<PetugasDesaController>(
|
if (!Get.isRegistered<PetugasDesaController>()) {
|
||||||
() => PetugasDesaController(),
|
Get.put(PetugasDesaController(), permanent: true);
|
||||||
fenix: true,
|
} else {
|
||||||
);
|
// Jika sudah terdaftar, gunakan find untuk mendapatkan instance yang ada
|
||||||
|
Get.find<PetugasDesaController>();
|
||||||
|
}
|
||||||
|
|
||||||
// Dashboard controller
|
// Dashboard controller
|
||||||
Get.lazyPut<PetugasDesaDashboardController>(
|
Get.lazyPut<PetugasDesaDashboardController>(
|
||||||
() => PetugasDesaDashboardController(),
|
() => PetugasDesaDashboardController(),
|
||||||
|
fenix: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Jadwal penyaluran controller
|
// Jadwal penyaluran controller
|
||||||
Get.lazyPut<JadwalPenyaluranController>(
|
Get.lazyPut<JadwalPenyaluranController>(
|
||||||
() => JadwalPenyaluranController(),
|
() => JadwalPenyaluranController(),
|
||||||
|
fenix: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Stok bantuan controller
|
// Stok bantuan controller
|
||||||
Get.lazyPut<StokBantuanController>(
|
Get.lazyPut<StokBantuanController>(
|
||||||
() => StokBantuanController(),
|
() => StokBantuanController(),
|
||||||
|
fenix: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Penitipan bantuan controller
|
// Penitipan bantuan controller
|
||||||
Get.lazyPut<PenitipanBantuanController>(
|
Get.lazyPut<PenitipanBantuanController>(
|
||||||
() => PenitipanBantuanController(),
|
() => PenitipanBantuanController(),
|
||||||
|
fenix: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pengaduan controller
|
// Pengaduan controller
|
||||||
Get.lazyPut<PengaduanController>(
|
Get.lazyPut<PengaduanController>(
|
||||||
() => PengaduanController(),
|
() => PengaduanController(),
|
||||||
|
fenix: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Penerima bantuan controller
|
// Penerima bantuan controller
|
||||||
Get.lazyPut<PenerimaBantuanController>(
|
Get.lazyPut<PenerimaBantuanController>(
|
||||||
() => PenerimaBantuanController(),
|
() => PenerimaBantuanController(),
|
||||||
|
fenix: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Laporan controller
|
// Laporan controller
|
||||||
Get.lazyPut<LaporanController>(
|
Get.lazyPut<LaporanController>(
|
||||||
() => LaporanController(),
|
() => LaporanController(),
|
||||||
|
fenix: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Jenis Bantuan: ${jadwalData['jenis_bantuan'] ?? ''}',
|
'Kategori Bantuan: ${jadwalData['kategori_bantuan'] ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
@ -181,7 +181,7 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Jenis: ${permintaanData['jenis_bantuan'] ?? ''}',
|
'Kategori: ${permintaanData['kategori_bantuan'] ?? ''}',
|
||||||
style: textTheme.bodySmall,
|
style: textTheme.bodySmall,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -17,8 +17,8 @@ class StokBantuanController extends GetxController {
|
|||||||
final RxDouble stokMasuk = 0.0.obs;
|
final RxDouble stokMasuk = 0.0.obs;
|
||||||
final RxDouble stokKeluar = 0.0.obs;
|
final RxDouble stokKeluar = 0.0.obs;
|
||||||
|
|
||||||
// Data untuk jenis bantuan
|
// Data untuk kategori bantuan
|
||||||
final RxList<Map<String, dynamic>> daftarJenisBantuan =
|
final RxList<Map<String, dynamic>> daftarKategoriBantuan =
|
||||||
<Map<String, dynamic>>[].obs;
|
<Map<String, dynamic>>[].obs;
|
||||||
|
|
||||||
// Controller untuk pencarian
|
// Controller untuk pencarian
|
||||||
@ -31,7 +31,7 @@ class StokBantuanController extends GetxController {
|
|||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
loadStokBantuanData();
|
loadStokBantuanData();
|
||||||
loadJenisBantuanData();
|
loadKategoriBantuanData();
|
||||||
|
|
||||||
// Listener untuk pencarian
|
// Listener untuk pencarian
|
||||||
searchController.addListener(() {
|
searchController.addListener(() {
|
||||||
@ -74,14 +74,14 @@ class StokBantuanController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadJenisBantuanData() async {
|
Future<void> loadKategoriBantuanData() async {
|
||||||
try {
|
try {
|
||||||
final jenisBantuanData = await _supabaseService.getJenisBantuan();
|
final kategoriBantuanData = await _supabaseService.getKategoriBantuan();
|
||||||
if (jenisBantuanData != null) {
|
if (kategoriBantuanData != null) {
|
||||||
daftarJenisBantuan.value = jenisBantuanData;
|
daftarKategoriBantuan.value = kategoriBantuanData;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await loadStokBantuanData();
|
await loadStokBantuanData();
|
||||||
await loadJenisBantuanData();
|
await loadKategoriBantuanData();
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -194,4 +194,18 @@ class StokBantuanController extends GetxController {
|
|||||||
.toList();
|
.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),
|
const SizedBox(height: 16),
|
||||||
_buildInfoItem(context,
|
_buildInfoItem(context,
|
||||||
icon: Icons.category,
|
icon: Icons.category,
|
||||||
label: 'Jenis Bantuan',
|
label: 'Kategori Bantuan',
|
||||||
value: jadwal['jenis_bantuan'] ?? '-'),
|
value: jadwal['kategori_bantuan'] ?? '-'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildInfoItem(context,
|
_buildInfoItem(context,
|
||||||
icon: Icons.calendar_today,
|
icon: Icons.calendar_today,
|
||||||
|
@ -169,7 +169,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
|||||||
{
|
{
|
||||||
'id': '1',
|
'id': '1',
|
||||||
'donatur': 'PT Sejahtera Abadi',
|
'donatur': 'PT Sejahtera Abadi',
|
||||||
'jenis_bantuan': 'Sembako',
|
'kategori_bantuan': 'Sembako',
|
||||||
'jumlah': '500 kg',
|
'jumlah': '500 kg',
|
||||||
'tanggal_pengajuan': '15 April 2023',
|
'tanggal_pengajuan': '15 April 2023',
|
||||||
'status': 'Menunggu',
|
'status': 'Menunggu',
|
||||||
@ -177,7 +177,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
|||||||
{
|
{
|
||||||
'id': '2',
|
'id': '2',
|
||||||
'donatur': 'Yayasan Peduli Sesama',
|
'donatur': 'Yayasan Peduli Sesama',
|
||||||
'jenis_bantuan': 'Pakaian',
|
'kategori_bantuan': 'Pakaian',
|
||||||
'jumlah': '200 pcs',
|
'jumlah': '200 pcs',
|
||||||
'tanggal_pengajuan': '14 April 2023',
|
'tanggal_pengajuan': '14 April 2023',
|
||||||
'status': 'Terverifikasi',
|
'status': 'Terverifikasi',
|
||||||
@ -185,7 +185,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
|||||||
{
|
{
|
||||||
'id': '3',
|
'id': '3',
|
||||||
'donatur': 'Bank BRI',
|
'donatur': 'Bank BRI',
|
||||||
'jenis_bantuan': 'Beras',
|
'kategori_bantuan': 'Beras',
|
||||||
'jumlah': '300 kg',
|
'jumlah': '300 kg',
|
||||||
'tanggal_pengajuan': '13 April 2023',
|
'tanggal_pengajuan': '13 April 2023',
|
||||||
'status': 'Terverifikasi',
|
'status': 'Terverifikasi',
|
||||||
@ -193,7 +193,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
|||||||
{
|
{
|
||||||
'id': '4',
|
'id': '4',
|
||||||
'donatur': 'Komunitas Peduli',
|
'donatur': 'Komunitas Peduli',
|
||||||
'jenis_bantuan': 'Alat Tulis',
|
'kategori_bantuan': 'Alat Tulis',
|
||||||
'jumlah': '100 set',
|
'jumlah': '100 set',
|
||||||
'tanggal_pengajuan': '12 April 2023',
|
'tanggal_pengajuan': '12 April 2023',
|
||||||
'status': 'Ditolak',
|
'status': 'Ditolak',
|
||||||
@ -304,8 +304,8 @@ class PenitipanView extends GetView<PetugasDesaController> {
|
|||||||
child: _buildItemDetail(
|
child: _buildItemDetail(
|
||||||
context,
|
context,
|
||||||
icon: Icons.category,
|
icon: Icons.category,
|
||||||
label: 'Jenis Bantuan',
|
label: 'Kategori Bantuan',
|
||||||
value: item['jenis_bantuan'] ?? '',
|
value: item['kategori_bantuan'] ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -23,7 +23,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
_showAddStokDialog(context);
|
_showAddStokDialog(context);
|
||||||
},
|
},
|
||||||
backgroundColor: AppTheme.primaryColor,
|
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(
|
child: _buildSummaryItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.inventory_2_outlined,
|
icon: Icons.inventory_2_outlined,
|
||||||
title: 'Total Stok',
|
title: 'Stok Tersedia',
|
||||||
value: DateFormatter.formatNumber(controller.totalStok.value),
|
value: DateFormatter.formatNumber(controller.totalStok.value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -87,7 +87,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
child: _buildSummaryItem(
|
child: _buildSummaryItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.input,
|
icon: Icons.input,
|
||||||
title: 'Masuk',
|
title: 'Total Masuk',
|
||||||
value: DateFormatter.formatNumber(controller.stokMasuk.value),
|
value: DateFormatter.formatNumber(controller.stokMasuk.value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -95,13 +95,48 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
child: _buildSummaryItem(
|
child: _buildSummaryItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.output,
|
icon: Icons.output,
|
||||||
title: 'Keluar',
|
title: 'Total Keluar',
|
||||||
value:
|
value:
|
||||||
DateFormatter.formatNumber(controller.stokKeluar.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 IconData icon,
|
||||||
required String title,
|
required String title,
|
||||||
required String value,
|
required String value,
|
||||||
|
Color? valueColor,
|
||||||
}) {
|
}) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@ -132,7 +168,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
value,
|
value,
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: valueColor ?? Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@ -276,9 +312,10 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
item.jenisBantuan != null
|
item.kategoriBantuan != null
|
||||||
? (item.jenisBantuan!['nama'] ?? 'Tidak Ada Jenis')
|
? (item.kategoriBantuan!['nama'] ??
|
||||||
: 'Tidak Ada Jenis',
|
'Tidak Ada Kategori')
|
||||||
|
: 'Tidak Ada Kategori',
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: AppTheme.primaryColor,
|
color: AppTheme.primaryColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -314,7 +351,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
context,
|
context,
|
||||||
icon: Icons.calendar_today,
|
icon: Icons.calendar_today,
|
||||||
label: 'Tanggal Masuk',
|
label: 'Tanggal Masuk',
|
||||||
value: DateFormatter.formatDate(item.tanggalMasuk),
|
value: DateFormatter.formatDateTime(item.tanggalMasuk),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -335,7 +372,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
|||||||
context,
|
context,
|
||||||
icon: Icons.access_time,
|
icon: Icons.access_time,
|
||||||
label: 'Terakhir Diperbarui',
|
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 satuanController = TextEditingController();
|
||||||
final deskripsiController = TextEditingController();
|
final deskripsiController = TextEditingController();
|
||||||
String? selectedJenisBantuanId;
|
String? selectedJenisBantuanId;
|
||||||
DateTime? tanggalMasuk = DateTime.now();
|
|
||||||
|
// Gunakan StatefulBuilder untuk memperbarui state dialog
|
||||||
|
DateTime tanggalMasuk = DateTime.now();
|
||||||
DateTime? tanggalKadaluarsa;
|
DateTime? tanggalKadaluarsa;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => StatefulBuilder(
|
||||||
title: const Text('Tambah Stok Bantuan'),
|
builder: (context, setState) => AlertDialog(
|
||||||
content: Form(
|
title: const Text('Tambah Stok Bantuan'),
|
||||||
key: formKey,
|
content: Form(
|
||||||
child: SingleChildScrollView(
|
key: formKey,
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
TextFormField(
|
children: [
|
||||||
controller: namaController,
|
TextFormField(
|
||||||
decoration: const InputDecoration(
|
controller: namaController,
|
||||||
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(
|
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Tanggal Masuk',
|
labelText: 'Nama Bantuan',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
child: Text(
|
validator: (value) {
|
||||||
DateFormatter.formatDate(tanggalMasuk),
|
if (value == null || value.isEmpty) {
|
||||||
),
|
return 'Nama bantuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
DropdownButtonFormField<String>(
|
||||||
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(
|
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Tanggal Kadaluarsa',
|
labelText: 'Kategori Bantuan',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
child: Text(
|
value: selectedJenisBantuanId,
|
||||||
DateFormatter.formatDate(tanggalKadaluarsa),
|
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());
|
TextEditingController(text: stok.jumlah?.toString());
|
||||||
final satuanController = TextEditingController(text: stok.satuan);
|
final satuanController = TextEditingController(text: stok.satuan);
|
||||||
final deskripsiController = TextEditingController(text: stok.deskripsi);
|
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? tanggalMasuk = stok.tanggalMasuk;
|
||||||
DateTime? tanggalKadaluarsa = stok.tanggalKadaluarsa;
|
DateTime? tanggalKadaluarsa = stok.tanggalKadaluarsa;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => StatefulBuilder(
|
||||||
title: const Text('Edit Stok Bantuan'),
|
builder: (context, setState) => AlertDialog(
|
||||||
content: Form(
|
title: const Text('Edit Stok Bantuan'),
|
||||||
key: formKey,
|
content: Form(
|
||||||
child: SingleChildScrollView(
|
key: formKey,
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
TextFormField(
|
children: [
|
||||||
controller: namaController,
|
TextFormField(
|
||||||
decoration: const InputDecoration(
|
controller: namaController,
|
||||||
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(
|
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Tanggal Masuk',
|
labelText: 'Nama Bantuan',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
child: Text(
|
validator: (value) {
|
||||||
DateFormatter.formatDate(tanggalMasuk),
|
if (value == null || value.isEmpty) {
|
||||||
),
|
return 'Nama bantuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
DropdownButtonFormField<String>(
|
||||||
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(
|
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Tanggal Kadaluarsa',
|
labelText: 'Kategori Bantuan',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
child: Text(
|
value: selectedJenisBantuanId,
|
||||||
DateFormatter.formatDate(tanggalKadaluarsa),
|
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'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
@ -14,12 +15,19 @@ class _SplashViewState extends State<SplashView> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_navigateToLogin();
|
_checkAuthAndNavigate();
|
||||||
}
|
}
|
||||||
|
|
||||||
_navigateToLogin() async {
|
_checkAuthAndNavigate() async {
|
||||||
|
// Tunggu 2 detik untuk menampilkan splash screen
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
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
|
@override
|
||||||
|
@ -9,6 +9,9 @@ class SupabaseService extends GetxService {
|
|||||||
// Cache untuk profil pengguna
|
// Cache untuk profil pengguna
|
||||||
Map<String, dynamic>? _cachedUserProfile;
|
Map<String, dynamic>? _cachedUserProfile;
|
||||||
|
|
||||||
|
// Flag untuk menandai apakah sesi sudah diinisialisasi
|
||||||
|
bool _isSessionInitialized = false;
|
||||||
|
|
||||||
// Ganti dengan URL dan API key Supabase Anda
|
// Ganti dengan URL dan API key Supabase Anda
|
||||||
static const String supabaseUrl = String.fromEnvironment('SUPABASE_URL',
|
static const String supabaseUrl = String.fromEnvironment('SUPABASE_URL',
|
||||||
defaultValue: 'http://labulabs.net:8000');
|
defaultValue: 'http://labulabs.net:8000');
|
||||||
@ -17,13 +20,47 @@ class SupabaseService extends GetxService {
|
|||||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzMxODYyODAwLAogICJleHAiOiAxODg5NjI5MjAwCn0.4IpwhwCVbfYXxb8JlZOLSBzCt6kQmypkvuso7N8Aicc');
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzMxODYyODAwLAogICJleHAiOiAxODg5NjI5MjAwCn0.4IpwhwCVbfYXxb8JlZOLSBzCt6kQmypkvuso7N8Aicc');
|
||||||
|
|
||||||
Future<SupabaseService> init() async {
|
Future<SupabaseService> init() async {
|
||||||
await Supabase.initialize(
|
try {
|
||||||
url: supabaseUrl,
|
await Supabase.initialize(
|
||||||
anonKey: supabaseKey,
|
url: supabaseUrl,
|
||||||
);
|
anonKey: supabaseKey,
|
||||||
|
debug: true, // Aktifkan debug untuk melihat log autentikasi
|
||||||
|
);
|
||||||
|
|
||||||
client = Supabase.instance.client;
|
client = Supabase.instance.client;
|
||||||
return this;
|
|
||||||
|
// 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
|
// Metode untuk mendaftar pengguna baru
|
||||||
@ -37,23 +74,52 @@ class SupabaseService extends GetxService {
|
|||||||
|
|
||||||
// Metode untuk login
|
// Metode untuk login
|
||||||
Future<AuthResponse> signIn(String email, String password) async {
|
Future<AuthResponse> signIn(String email, String password) async {
|
||||||
return await client.auth.signInWithPassword(
|
final response = await client.auth.signInWithPassword(
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (response.user != null) {
|
||||||
|
_isSessionInitialized = true;
|
||||||
|
print('DEBUG: Login berhasil, sesi diinisialisasi');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk logout
|
// Metode untuk logout
|
||||||
Future<void> signOut() async {
|
Future<void> signOut() async {
|
||||||
_cachedUserProfile = null; // Hapus cache saat logout
|
_cachedUserProfile = null; // Hapus cache saat logout
|
||||||
|
_isSessionInitialized = false;
|
||||||
await client.auth.signOut();
|
await client.auth.signOut();
|
||||||
|
print('DEBUG: Logout berhasil, sesi dihapus');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan user saat ini
|
// Metode untuk mendapatkan user saat ini
|
||||||
User? get currentUser => client.auth.currentUser;
|
User? get currentUser => client.auth.currentUser;
|
||||||
|
|
||||||
// Metode untuk memeriksa apakah user sudah login
|
// 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
|
// Metode untuk mendapatkan profil pengguna
|
||||||
Future<Map<String, dynamic>?> getUserProfile() async {
|
Future<Map<String, dynamic>?> getUserProfile() async {
|
||||||
@ -270,7 +336,7 @@ class SupabaseService extends GetxService {
|
|||||||
try {
|
try {
|
||||||
final response = await client
|
final response = await client
|
||||||
.from('stok_bantuan')
|
.from('stok_bantuan')
|
||||||
.select('*, jenis_bantuan:jenis_bantuan_id(id, nama)');
|
.select('*, kategori_bantuan:kategori_bantuan_id(id, nama)');
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} 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 {
|
try {
|
||||||
final response = await client.from('jenis_bantuan').select('*');
|
final response = await client.from('kategori_bantuan').select('*');
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getting jenis bantuan: $e');
|
print('Error getting kategori bantuan: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addStok(Map<String, dynamic> stokData) async {
|
Future<void> addStok(Map<String, dynamic> stokData) async {
|
||||||
try {
|
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);
|
await client.from('stok_bantuan').insert(stokData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error adding stok: $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,
|
static String formatNumber(num? number,
|
||||||
{String locale = 'id_ID', String defaultValue = '0'}) {
|
{String locale = 'id_ID', String defaultValue = '0'}) {
|
||||||
if (number == null) return defaultValue;
|
if (number == null) return defaultValue;
|
||||||
|
Reference in New Issue
Block a user