diff --git a/lib/app/data/models/pengajuan_kelayakan_bantuan_model.dart b/lib/app/data/models/pengajuan_kelayakan_bantuan_model.dart index ebabf49..368387b 100644 --- a/lib/app/data/models/pengajuan_kelayakan_bantuan_model.dart +++ b/lib/app/data/models/pengajuan_kelayakan_bantuan_model.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -enum StatusKelayakan { pending, disetujui, ditolak } +enum StatusKelayakan { MENUNGGU, TERVERIFIKASI, DITOLAK } class PengajuanKelayakanBantuanModel { final String? id; diff --git a/lib/app/modules/petugas_desa/components/calendar_view_widget.dart b/lib/app/modules/petugas_desa/components/calendar_view_widget.dart index 3e7ede5..c29aa3b 100644 --- a/lib/app/modules/petugas_desa/components/calendar_view_widget.dart +++ b/lib/app/modules/petugas_desa/components/calendar_view_widget.dart @@ -245,7 +245,7 @@ class CalendarViewWidget extends StatelessWidget { List allJadwal = [ ...controller.jadwalHariIni, ...controller.jadwalMendatang, - ...controller.jadwalSelesai, + ...controller.jadwalTerlaksana, ]; DateTime now = DateTime.now(); diff --git a/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart index 0392e2d..4e95f27 100644 --- a/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart +++ b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart @@ -92,8 +92,10 @@ class JadwalSectionWidget extends StatelessWidget { return Icons.event_note; case 'Terjadwal': return Icons.pending_actions; - case 'Selesai': + case 'Terlaksana': return Icons.event_available; + case 'Tidak Terlaksana': + return Icons.event_busy; default: return Icons.event_note; } @@ -105,8 +107,9 @@ class JadwalSectionWidget extends StatelessWidget { return Icons.calendar_today; case 'Terjadwal': return Icons.schedule; - case 'Selesai': + case 'Terlaksana': return Icons.task_alt; + default: return Icons.event_note; } @@ -118,7 +121,7 @@ class JadwalSectionWidget extends StatelessWidget { return Colors.green; case 'Terjadwal': return Colors.blue; - case 'Selesai': + case 'Terlaksana': return Colors.grey; default: return Colors.orange; @@ -127,16 +130,18 @@ class JadwalSectionWidget extends StatelessWidget { String _getStatusText(PenyaluranBantuanModel jadwal) { // Jika status jadwal adalah BERLANGSUNG, tampilkan sebagai "Aktif" - if (jadwal.status == 'BERLANGSUNG') { + if (jadwal.status == 'AKTIF') { return 'Aktif'; } // Jika status jadwal adalah DIJADWALKAN, tampilkan sebagai "Terjadwal" - else if (jadwal.status == 'DIJADWALKAN' || jadwal.status == 'DISETUJUI') { + else if (jadwal.status == 'DIJADWALKAN') { return 'Terjadwal'; } - // Jika status jadwal adalah SELESAI, tampilkan sebagai "Selesai" - else if (jadwal.status == 'SELESAI') { - return 'Selesai'; + // Jika status jadwal adalah terlaksana, tampilkan sebagai "Terlaksana" + else if (jadwal.status == 'TERLAKSANA') { + return 'Terlaksana'; + } else if (jadwal.status == 'BATALTERLAKSANA') { + return 'Batal Terlaksana'; } // Default status return status; @@ -144,16 +149,16 @@ class JadwalSectionWidget extends StatelessWidget { Color _getStatusColorByJadwal(PenyaluranBantuanModel jadwal) { // Jika status jadwal adalah BERLANGSUNG, gunakan warna hijau - if (jadwal.status == 'BERLANGSUNG') { + if (jadwal.status == 'AKTIF') { return Colors.green; } // Jika status jadwal adalah DIJADWALKAN, gunakan warna biru - else if (jadwal.status == 'DIJADWALKAN' || jadwal.status == 'DISETUJUI') { + else if (jadwal.status == 'DIJADWALKAN') { return Colors.blue; - } - // Jika status jadwal adalah SELESAI, gunakan warna abu-abu - else if (jadwal.status == 'SELESAI') { + } else if (jadwal.status == 'TERLAKSANA') { return Colors.grey; + } else if (jadwal.status == 'BATALTERLAKSANA') { + return Colors.red; } // Default warna return _getStatusColor(); @@ -165,8 +170,8 @@ class JadwalSectionWidget extends StatelessWidget { return controller.jadwalHariIni.toList(); case 'Mendatang': return controller.jadwalMendatang.toList(); - case 'Selesai': - return controller.jadwalSelesai.toList(); + case 'Terlaksana': + return controller.jadwalTerlaksana.toList(); default: return jadwalList; } diff --git a/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart b/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart index 185028f..6f07474 100644 --- a/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart @@ -4,6 +4,7 @@ import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart'; import 'package:penyaluran_app/app/data/models/lokasi_penyaluran_model.dart'; import 'package:penyaluran_app/app/data/models/kategori_bantuan_model.dart'; import 'package:penyaluran_app/app/data/models/user_model.dart'; +import 'package:penyaluran_app/app/data/models/skema_bantuan_model.dart'; import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart'; import 'package:penyaluran_app/app/services/supabase_service.dart'; import 'package:penyaluran_app/app/utils/date_time_helper.dart'; @@ -13,6 +14,8 @@ class JadwalPenyaluranController extends GetxController { final AuthController _authController = Get.find(); final SupabaseService _supabaseService = SupabaseService.to; + SupabaseService get supabaseService => _supabaseService; + final RxBool isLoading = false.obs; // Indeks kategori yang dipilih untuk filter @@ -23,7 +26,7 @@ class JadwalPenyaluranController extends GetxController { [].obs; final RxList jadwalMendatang = [].obs; - final RxList jadwalSelesai = + final RxList jadwalTerlaksana = [].obs; // Data untuk permintaan penjadwalan @@ -36,6 +39,8 @@ class JadwalPenyaluranController extends GetxController { {}.obs; final RxMap kategoriBantuanCache = {}.obs; + final RxMap skemaBantuanCache = + {}.obs; // Controller untuk pencarian final TextEditingController searchController = TextEditingController(); @@ -49,6 +54,7 @@ class JadwalPenyaluranController extends GetxController { loadPermintaanPenjadwalanData(); loadLokasiPenyaluranData(); loadKategoriBantuanData(); + loadSkemaBantuanData(); // Jalankan timer untuk memeriksa jadwal secara berkala _startJadwalCheckTimer(); @@ -89,8 +95,9 @@ class JadwalPenyaluranController extends GetxController { // Periksa jadwal mendatang yang tanggalnya hari ini List jadwalToUpdate = []; + List jadwalTerlewat = []; - for (var jadwal in jadwalMendatang) { + for (var jadwal in jadwalHariIni) { if (jadwal.tanggalPenyaluran != null) { // Konversi tanggal jadwal ke timezone lokal final jadwalDateTime = @@ -103,32 +110,50 @@ class JadwalPenyaluranController extends GetxController { // Jika tanggal jadwal adalah hari ini if (isSameDay(jadwalDate, today)) { - jadwalToUpdate.add(jadwal); - // Jika waktu jadwal sudah tiba atau lewat if (now.isAfter(jadwalDateTime) || now.isAtSameMomentAs(jadwalDateTime)) { - // Ubah status menjadi BERLANGSUNG (aktif) - await _supabaseService.updateJadwalStatus( - jadwal.id!, 'BERLANGSUNG'); + if (jadwal.status == 'DIJADWALKAN') { + // Jika status masih DIJADWALKAN, ubah menjadi BATALTERLAKSANA + await _supabaseService.updateJadwalStatus( + jadwal.id!, 'BATALTERLAKSANA'); + jadwalTerlewat.add(jadwal); + } else if (jadwal.status == 'AKTIF') { + // Jika status BERLANGSUNG, tambahkan ke daftar update + jadwalToUpdate.add(jadwal); + } } } } } // Refresh data setelah pembaruan - if (jadwalToUpdate.isNotEmpty) { + if (jadwalToUpdate.isNotEmpty || jadwalTerlewat.isNotEmpty) { await loadJadwalData(); // Tampilkan notifikasi jika ada jadwal yang dipindahkan - Get.snackbar( - 'Jadwal Diperbarui', - '${jadwalToUpdate.length} jadwal dipindahkan ke section Hari Ini', - snackPosition: SnackPosition.TOP, - backgroundColor: Colors.green, - colorText: Colors.white, - duration: const Duration(seconds: 3), - ); + if (jadwalToUpdate.isNotEmpty) { + Get.snackbar( + 'Jadwal Diperbarui', + '${jadwalToUpdate.length} jadwal dipindahkan ke section Hari Ini', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + } + + // Tampilkan notifikasi jika ada jadwal yang terlewat + if (jadwalTerlewat.isNotEmpty) { + Get.snackbar( + 'Jadwal Terlewat', + '${jadwalTerlewat.length} jadwal diubah menjadi BATALTERLAKSANA', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.orange, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + } } } catch (e) { print('Error checking and updating jadwal status: $e'); @@ -162,9 +187,9 @@ class JadwalPenyaluranController extends GetxController { } // Mengambil data jadwal selesai - final jadwalSelesaiData = await _supabaseService.getJadwalSelesai(); - if (jadwalSelesaiData != null) { - jadwalSelesai.value = jadwalSelesaiData + final jadwalTerlaksanaData = await _supabaseService.getJadwalTerlaksana(); + if (jadwalTerlaksanaData != null) { + jadwalTerlaksana.value = jadwalTerlaksanaData .map((data) => PenyaluranBantuanModel.fromJson(data)) .toList(); } @@ -219,6 +244,22 @@ class JadwalPenyaluranController extends GetxController { } } + Future loadSkemaBantuanData() async { + try { + final skemaData = await _supabaseService.getAllSkemaBantuan(); + if (skemaData != null) { + for (var skema in skemaData) { + final skemaModel = SkemaBantuanModel.fromJson(skema); + if (skemaModel.id != null) { + skemaBantuanCache[skemaModel.id!] = skemaModel; + } + } + } + } catch (e) { + print('Error loading skema bantuan data: $e'); + } + } + // Mendapatkan nama lokasi penyaluran berdasarkan ID String getLokasiPenyaluranName(String? lokasiId) { if (lokasiId == null) return 'Lokasi tidak diketahui'; @@ -333,7 +374,7 @@ class JadwalPenyaluranController extends GetxController { Future tambahPenyaluran({ required String nama, required String deskripsi, - required String kategoriBantuanId, + required String skemaId, required String lokasiPenyaluranId, required int jumlahPenerima, required DateTime? tanggalPenyaluran, @@ -349,7 +390,7 @@ class JadwalPenyaluranController extends GetxController { final penyaluran = { 'nama': nama, 'deskripsi': deskripsi, - 'kategori_bantuan_id': kategoriBantuanId, + 'skema_id': skemaId, 'lokasi_penyaluran_id': lokasiPenyaluranId, 'petugas_id': user!.id, 'jumlah_penerima': jumlahPenerima, @@ -357,8 +398,30 @@ class JadwalPenyaluranController extends GetxController { 'status': 'DIJADWALKAN', // Status awal adalah terjadwal }; - // Simpan ke database - await _supabaseService.tambahPenyaluran(penyaluran); + // Simpan ke database dan dapatkan ID penyaluran + final response = await _supabaseService.tambahPenyaluran(penyaluran); + final penyaluranId = response['id']; + + // Ambil data pengajuan kelayakan bantuan yang disetujui + final pengajuanData = await _supabaseService.client + .from('xx02_pengajuan_kelayakan_bantuan') + .select('*') + .eq('skema_bantuan_id', skemaId) + .eq('status', 'TERVERIFIKASI'); + + // Buat data penerima penyaluran untuk setiap pengajuan yang disetujui + for (var pengajuan in pengajuanData) { + final penerimaPenyaluran = { + 'penyaluran_bantuan_id': penyaluranId, + 'warga_id': pengajuan['warga_id'], + 'stok_bantuan_id': skemaBantuanCache[skemaId]?.stokBantuanId, + 'status_penerimaan': 'MENUNGGU', + }; + + await _supabaseService.client + .from('penerima_penyaluran') + .insert(penerimaPenyaluran); + } // Refresh data await loadJadwalData(); diff --git a/lib/app/modules/petugas_desa/views/penyaluran_view.dart b/lib/app/modules/petugas_desa/views/penyaluran_view.dart index 9658818..504788c 100644 --- a/lib/app/modules/petugas_desa/views/penyaluran_view.dart +++ b/lib/app/modules/petugas_desa/views/penyaluran_view.dart @@ -101,9 +101,9 @@ class PenyaluranView extends GetView { // Jadwal selesai JadwalSectionWidget( controller: controller, - title: 'Selesai', - jadwalList: controller.jadwalSelesai, - status: 'Selesai', + title: 'Terlaksana', + jadwalList: controller.jadwalTerlaksana, + status: 'Terlaksana', ), ], ); @@ -190,9 +190,9 @@ class PenyaluranView extends GetView { Expanded( child: Obx(() => _buildSummaryItem( context, - icon: Icons.event_busy, - title: 'Selesai', - value: '${controller.jadwalSelesai.length}', + icon: Icons.event_note, + title: 'Terlaksana', + value: '${controller.jadwalTerlaksana.length}', color: Colors.grey, )), ), diff --git a/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart b/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart index 0d58ebe..f1a21d1 100644 --- a/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart +++ b/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart @@ -3,6 +3,8 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart'; import 'package:penyaluran_app/app/theme/app_theme.dart'; +import 'package:penyaluran_app/app/data/models/skema_bantuan_model.dart'; +import 'package:penyaluran_app/app/data/models/pengajuan_kelayakan_bantuan_model.dart'; class TambahPenyaluranView extends GetView { const TambahPenyaluranView({super.key}); @@ -23,21 +25,38 @@ class TambahPenyaluranView extends GetView { final formKey = GlobalKey(); final TextEditingController namaController = TextEditingController(); final TextEditingController deskripsiController = TextEditingController(); - final TextEditingController jumlahPenerimaController = - TextEditingController(); final TextEditingController tanggalPenyaluranController = TextEditingController(); final TextEditingController waktuPenyaluranController = TextEditingController(); // Variabel untuk menyimpan nilai yang dipilih - final Rx selectedKategoriBantuanId = Rx(null); + final Rx selectedSkemaBantuanId = Rx(null); final Rx selectedLokasiPenyaluranId = Rx(null); + final Rx selectedSkemaBantuan = + Rx(null); + final RxInt jumlahPenerima = 0.obs; // Tanggal dan waktu penyaluran final Rx selectedDate = Rx(null); final Rx selectedTime = Rx(null); + // Fungsi untuk memuat data pengajuan kelayakan bantuan + Future loadPengajuanKelayakan(String skemaId) async { + try { + final pengajuanData = await controller.supabaseService.client + .from('xx02_pengajuan_kelayakan_bantuan') + .select('*') + .eq('skema_bantuan_id', skemaId) + .eq('status', 'TERVERIFIKASI'); + print('pengajuan $pengajuanData'); + + jumlahPenerima.value = pengajuanData.length; + } catch (e) { + print('Error loading pengajuan kelayakan: $e'); + } + } + return Padding( padding: const EdgeInsets.all(16.0), child: Form( @@ -82,9 +101,9 @@ class TambahPenyaluranView extends GetView { ), const SizedBox(height: 16), - // Kategori Bantuan + // Skema Bantuan Text( - 'Kategori Bantuan', + 'Skema Bantuan', style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 8), @@ -98,20 +117,25 @@ class TambahPenyaluranView extends GetView { vertical: 8, ), ), - hint: const Text('Pilih kategori bantuan'), - value: selectedKategoriBantuanId.value, - items: controller.kategoriBantuanCache.entries + hint: const Text('Pilih skema bantuan'), + value: selectedSkemaBantuanId.value, + items: controller.skemaBantuanCache.entries .map((entry) => DropdownMenuItem( value: entry.key, child: Text(entry.value.nama ?? 'Tidak ada nama'), )) .toList(), - onChanged: (value) { - selectedKategoriBantuanId.value = value; + onChanged: (value) async { + selectedSkemaBantuanId.value = value; + if (value != null) { + selectedSkemaBantuan.value = + controller.skemaBantuanCache[value]; + await loadPengajuanKelayakan(value); + } }, validator: (value) { if (value == null || value.isEmpty) { - return 'Kategori bantuan harus dipilih'; + return 'Skema bantuan harus dipilih'; } return null; }, @@ -154,38 +178,117 @@ class TambahPenyaluranView extends GetView { )), const SizedBox(height: 16), - // Jumlah Penerima + // Jumlah Penerima (Otomatis) Text( 'Jumlah Penerima', style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 8), - TextFormField( - controller: jumlahPenerimaController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: 'Masukkan jumlah penerima', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Jumlah penerima tidak boleh kosong'; - } - if (int.tryParse(value) == null) { - return 'Jumlah penerima harus berupa angka'; - } - if (int.parse(value) <= 0) { - return 'Jumlah penerima harus lebih dari 0'; - } - return null; - }, - ), + Obx(() => TextFormField( + readOnly: true, + controller: TextEditingController( + text: jumlahPenerima.value.toString()), + decoration: InputDecoration( + hintText: 'Jumlah penerima akan diambil otomatis', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + ), + )), + const SizedBox(height: 8), + Obx(() => jumlahPenerima.value > 0 + ? TextButton.icon( + onPressed: () async { + final pengajuanData = await controller + .supabaseService.client + .from('xx02_pengajuan_kelayakan_bantuan') + .select('*, warga:warga_id(*)') + .eq('skema_bantuan_id', + selectedSkemaBantuanId.value ?? '') + .eq('status', 'TERVERIFIKASI'); + + if (pengajuanData != null) { + Get.dialog( + Dialog( + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + height: + MediaQuery.of(context).size.height * 0.8, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Daftar Penerima Bantuan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + IconButton( + onPressed: () => Get.back(), + icon: const Icon(Icons.close), + ), + ], + ), + const SizedBox(height: 16), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + child: DataTable( + columnSpacing: 20, + horizontalMargin: 20, + columns: const [ + DataColumn(label: Text('No')), + DataColumn(label: Text('Nama')), + DataColumn(label: Text('NIK')), + DataColumn(label: Text('Alamat')), + ], + rows: pengajuanData + .asMap() + .entries + .map((entry) { + final warga = + entry.value['warga']; + return DataRow( + cells: [ + DataCell( + Text('${entry.key + 1}')), + DataCell(Text( + warga['nama_lengkap'] ?? + '-')), + DataCell(Text( + warga['nik'] ?? '-')), + DataCell(Text( + warga['alamat'] ?? '-')), + ], + ); + }).toList(), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } + }, + icon: const Icon(Icons.people), + label: const Text('Lihat Daftar Penerima'), + ) + : const SizedBox.shrink()), const SizedBox(height: 16), // Tanggal Penyaluran @@ -321,10 +424,9 @@ class TambahPenyaluranView extends GetView { controller.tambahPenyaluran( nama: namaController.text, deskripsi: deskripsiController.text, - kategoriBantuanId: selectedKategoriBantuanId.value!, + skemaId: selectedSkemaBantuanId.value!, lokasiPenyaluranId: selectedLokasiPenyaluranId.value!, - jumlahPenerima: - int.parse(jumlahPenerimaController.text), + jumlahPenerima: jumlahPenerima.value, tanggalPenyaluran: tanggalWaktuPenyaluran, ); } diff --git a/lib/app/services/supabase_service.dart b/lib/app/services/supabase_service.dart index 4c0d98a..251931e 100644 --- a/lib/app/services/supabase_service.dart +++ b/lib/app/services/supabase_service.dart @@ -242,7 +242,7 @@ class SupabaseService extends GetxService { .select('*') .gte('tanggal_penyaluran', todayUtc) .lt('tanggal_penyaluran', tomorrowUtc) - .inFilter('status', ['DISETUJUI', 'BERLANGSUNG', 'DIJADWALKAN']); + .inFilter('status', ['AKTIF', 'DIJADWALKAN']); return response; } catch (e) { @@ -267,7 +267,7 @@ class SupabaseService extends GetxService { .select('*') .gte('tanggal_penyaluran', tomorrowUtc) .lt('tanggal_penyaluran', weekUtc) - .inFilter('status', ['DISETUJUI', 'DIJADWALKAN']); + .inFilter('status', ['DIJADWALKAN']); return response; } catch (e) { @@ -276,12 +276,12 @@ class SupabaseService extends GetxService { } } - Future>?> getJadwalSelesai() async { + Future>?> getJadwalTerlaksana() async { try { final response = await client .from('penyaluran_bantuan') .select('*') - .eq('status', 'SELESAI') + .eq('status', 'TERLAKSANA') .order('tanggal_penyaluran', ascending: false) .limit(10); @@ -1124,12 +1124,33 @@ class SupabaseService extends GetxService { } // Fungsi untuk menambahkan penyaluran baru - Future tambahPenyaluran(Map penyaluran) async { + Future> tambahPenyaluran( + Map penyaluran) async { try { - await client.from('penyaluran_bantuan').insert(penyaluran); + final response = await client + .from('penyaluran_bantuan') + .insert(penyaluran) + .select() + .single(); + + return response; } catch (e) { print('Error menambahkan penyaluran: $e'); throw e.toString(); } } + + Future>?> getAllSkemaBantuan() async { + try { + final response = await client + .from('xx02_skema_bantuan') + .select('*') + .order('created_at', ascending: false); + + return response; + } catch (e) { + print('Error getting all skema bantuan: $e'); + return null; + } + } }