From 675b0a7cad89eebf8f348460a970cd9495d35509 Mon Sep 17 00:00:00 2001 From: Khafidh Fuadi Date: Sat, 15 Mar 2025 00:03:00 +0700 Subject: [PATCH] Tambahkan properti tanggalWaktuSelesai pada model PenyaluranBantuanModel dan perbarui tampilan serta controller untuk mendukung rentang waktu penyaluran. Modifikasi logika di JadwalPenyaluranController untuk memperbarui status jadwal berdasarkan waktu mulai dan selesai. Perbarui tampilan TambahPenyaluranView untuk memungkinkan pemilihan waktu mulai dan selesai secara terpisah. --- .../data/models/penyaluran_bantuan_model.dart | 6 + .../jadwal_penyaluran_controller.dart | 36 ++- .../views/tambah_penyaluran_view.dart | 256 +++++++++++++++--- lib/app/services/supabase_service.dart | 2 + 4 files changed, 242 insertions(+), 58 deletions(-) diff --git a/lib/app/data/models/penyaluran_bantuan_model.dart b/lib/app/data/models/penyaluran_bantuan_model.dart index dc1d07a..54f92a2 100644 --- a/lib/app/data/models/penyaluran_bantuan_model.dart +++ b/lib/app/data/models/penyaluran_bantuan_model.dart @@ -9,6 +9,7 @@ class PenyaluranBantuanModel { final String? status; final String? alasanPenolakan; final DateTime? tanggalPenyaluran; + final DateTime? tanggalWaktuSelesai; final String? kategoriBantuanId; final DateTime? tanggalPermintaan; final int? jumlahPenerima; @@ -25,6 +26,7 @@ class PenyaluranBantuanModel { this.status, this.alasanPenolakan, this.tanggalPenyaluran, + this.tanggalWaktuSelesai, this.kategoriBantuanId, this.tanggalPermintaan, this.jumlahPenerima, @@ -50,6 +52,9 @@ class PenyaluranBantuanModel { tanggalPenyaluran: json["tanggal_penyaluran"] != null ? DateTime.parse(json["tanggal_penyaluran"]).toUtc() : null, + tanggalWaktuSelesai: json["tanggal_waktu_selesai"] != null + ? DateTime.parse(json["tanggal_waktu_selesai"]).toUtc() + : null, kategoriBantuanId: json["kategori_bantuan_id"], tanggalPermintaan: json["tanggal_permintaan"] != null ? DateTime.parse(json["tanggal_permintaan"]).toUtc() @@ -73,6 +78,7 @@ class PenyaluranBantuanModel { "status": status, "alasan_penolakan": alasanPenolakan, "tanggal_penyaluran": tanggalPenyaluran?.toUtc().toIso8601String(), + "tanggal_waktu_selesai": tanggalWaktuSelesai?.toUtc().toIso8601String(), "kategori_bantuan_id": kategoriBantuanId, "tanggal_permintaan": tanggalPermintaan?.toUtc().toIso8601String(), "jumlah_penerima": jumlahPenerima, 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 6f07474..9cd889d 100644 --- a/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart @@ -89,17 +89,14 @@ class JadwalPenyaluranController extends GetxController { // Memeriksa dan memperbarui status jadwal Future checkAndUpdateJadwalStatus() async { try { - // Dapatkan tanggal dan waktu saat ini dalam timezone lokal final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); - // Periksa jadwal mendatang yang tanggalnya hari ini List jadwalToUpdate = []; List jadwalTerlewat = []; for (var jadwal in jadwalHariIni) { if (jadwal.tanggalPenyaluran != null) { - // Konversi tanggal jadwal ke timezone lokal final jadwalDateTime = DateTimeHelper.toLocalDateTime(jadwal.tanggalPenyaluran!); final jadwalDate = DateTime( @@ -108,30 +105,35 @@ class JadwalPenyaluranController extends GetxController { jadwalDateTime.day, ); - // Jika tanggal jadwal adalah hari ini if (isSameDay(jadwalDate, today)) { - // Jika waktu jadwal sudah tiba atau lewat if (now.isAfter(jadwalDateTime) || now.isAtSameMomentAs(jadwalDateTime)) { if (jadwal.status == 'DIJADWALKAN') { - // Jika status masih DIJADWALKAN, ubah menjadi BATALTERLAKSANA - await _supabaseService.updateJadwalStatus( - jadwal.id!, 'BATALTERLAKSANA'); - jadwalTerlewat.add(jadwal); + if (now + .isBefore(jadwalDateTime.add(const Duration(hours: 2)))) { + await _supabaseService.updateJadwalStatus( + jadwal.id!, 'AKTIF'); + jadwalToUpdate.add(jadwal); + } else { + await _supabaseService.updateJadwalStatus( + jadwal.id!, 'BATALTERLAKSANA'); + jadwalTerlewat.add(jadwal); + } } else if (jadwal.status == 'AKTIF') { - // Jika status BERLANGSUNG, tambahkan ke daftar update - jadwalToUpdate.add(jadwal); + if (now.isAfter(jadwalDateTime.add(const Duration(hours: 2)))) { + await _supabaseService.updateJadwalStatus( + jadwal.id!, 'BATALTERLAKSANA'); + jadwalTerlewat.add(jadwal); + } } } } } } - // Refresh data setelah pembaruan if (jadwalToUpdate.isNotEmpty || jadwalTerlewat.isNotEmpty) { await loadJadwalData(); - // Tampilkan notifikasi jika ada jadwal yang dipindahkan if (jadwalToUpdate.isNotEmpty) { Get.snackbar( 'Jadwal Diperbarui', @@ -143,7 +145,6 @@ class JadwalPenyaluranController extends GetxController { ); } - // Tampilkan notifikasi jika ada jadwal yang terlewat if (jadwalTerlewat.isNotEmpty) { Get.snackbar( 'Jadwal Terlewat', @@ -155,8 +156,9 @@ class JadwalPenyaluranController extends GetxController { ); } } - } catch (e) { + } catch (e, stackTrace) { print('Error checking and updating jadwal status: $e'); + print('Stack trace: $stackTrace'); } } @@ -375,9 +377,11 @@ class JadwalPenyaluranController extends GetxController { required String nama, required String deskripsi, required String skemaId, + required String kategoriBantuanId, required String lokasiPenyaluranId, required int jumlahPenerima, required DateTime? tanggalPenyaluran, + DateTime? tanggalWaktuSelesai, }) async { isLoading.value = true; try { @@ -395,7 +399,9 @@ class JadwalPenyaluranController extends GetxController { 'petugas_id': user!.id, 'jumlah_penerima': jumlahPenerima, 'tanggal_penyaluran': tanggalPenyaluran?.toUtc().toIso8601String(), + 'tanggal_waktu_selesai': tanggalWaktuSelesai?.toUtc().toIso8601String(), 'status': 'DIJADWALKAN', // Status awal adalah terjadwal + 'kategori_bantuan_id': kategoriBantuanId, }; // Simpan ke database dan dapatkan ID penyaluran 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 f1a21d1..6a2e002 100644 --- a/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart +++ b/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart @@ -4,7 +4,6 @@ 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}); @@ -27,7 +26,8 @@ class TambahPenyaluranView extends GetView { final TextEditingController deskripsiController = TextEditingController(); final TextEditingController tanggalPenyaluranController = TextEditingController(); - final TextEditingController waktuPenyaluranController = + final TextEditingController waktuMulaiController = TextEditingController(); + final TextEditingController waktuSelesaiController = TextEditingController(); // Variabel untuk menyimpan nilai yang dipilih @@ -39,7 +39,8 @@ class TambahPenyaluranView extends GetView { // Tanggal dan waktu penyaluran final Rx selectedDate = Rx(null); - final Rx selectedTime = Rx(null); + final Rx selectedWaktuMulai = Rx(null); + final Rx selectedWaktuSelesai = Rx(null); // Fungsi untuk memuat data pengajuan kelayakan bantuan Future loadPengajuanKelayakan(String skemaId) async { @@ -200,6 +201,15 @@ class TambahPenyaluranView extends GetView { ), )), const SizedBox(height: 8), + Text( + 'Info: Jumlah penerima diambil dari data pengajuan kelayakan bantuan yang telah terverifikasi untuk skema bantuan yang dipilih.', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + fontStyle: FontStyle.italic, + ), + ), + const SizedBox(height: 16), Obx(() => jumlahPenerima.value > 0 ? TextButton.icon( onPressed: () async { @@ -312,10 +322,13 @@ class TambahPenyaluranView extends GetView { suffixIcon: const Icon(Icons.calendar_today), ), onTap: () async { + // Tanggal minimal adalah 1 hari setelah hari ini + final DateTime tomorrow = + DateTime.now().add(const Duration(days: 1)); final DateTime? pickedDate = await showDatePicker( context: context, - initialDate: DateTime.now(), - firstDate: DateTime.now(), + initialDate: tomorrow, + firstDate: tomorrow, lastDate: DateTime.now().add(const Duration(days: 365)), ); if (pickedDate != null) { @@ -331,45 +344,186 @@ class TambahPenyaluranView extends GetView { return null; }, ), + const SizedBox(height: 10), + + // Hint info tanggal minimal + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blue[200]!), + ), + child: Row( + children: [ + const Icon( + Icons.info_outline, + color: Colors.blue, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Tanggal pelaksanaan minimal 1 hari sebelum dijadwalkan', + style: TextStyle( + color: Colors.blue[900], + fontSize: 12, + ), + ), + ), + ], + ), + ), const SizedBox(height: 16), - // Waktu Penyaluran + // Rentang Waktu Penyaluran Text( - 'Waktu Penyaluran', + 'Rentang Waktu Penyaluran', style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 8), - TextFormField( - controller: waktuPenyaluranController, - readOnly: true, - decoration: InputDecoration( - hintText: 'Pilih waktu penyaluran', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + + // Waktu Mulai + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Waktu Mulai'), + const SizedBox(height: 4), + TextFormField( + controller: waktuMulaiController, + readOnly: true, + decoration: InputDecoration( + hintText: 'Mulai', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + suffixIcon: const Icon(Icons.access_time), + ), + onTap: () async { + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (pickedTime != null) { + selectedWaktuMulai.value = pickedTime; + waktuMulaiController.text = + '${pickedTime.hour.toString().padLeft(2, '0')}:${pickedTime.minute.toString().padLeft(2, '0')}'; + + // Jika waktu selesai belum dipilih atau lebih awal dari waktu mulai + if (selectedWaktuSelesai.value == null || + (selectedWaktuSelesai.value != null && + (pickedTime.hour > + selectedWaktuSelesai + .value!.hour || + (pickedTime.hour == + selectedWaktuSelesai + .value!.hour && + pickedTime.minute >= + selectedWaktuSelesai + .value!.minute)))) { + // Set waktu selesai 1 jam setelah waktu mulai + final TimeOfDay defaultSelesai = TimeOfDay( + hour: (pickedTime.hour + 1) % 24, + minute: pickedTime.minute, + ); + selectedWaktuSelesai.value = defaultSelesai; + waktuSelesaiController.text = + '${defaultSelesai.hour.toString().padLeft(2, '0')}:${defaultSelesai.minute.toString().padLeft(2, '0')}'; + } + } + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Waktu mulai harus dipilih'; + } + return null; + }, + ), + ], + ), ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Waktu Selesai'), + const SizedBox(height: 4), + TextFormField( + controller: waktuSelesaiController, + readOnly: true, + decoration: InputDecoration( + hintText: 'Selesai', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + suffixIcon: const Icon(Icons.access_time), + ), + onTap: () async { + // Pastikan waktu mulai sudah dipilih + if (selectedWaktuMulai.value == null) { + Get.snackbar( + 'Perhatian', + 'Silakan pilih waktu mulai terlebih dahulu', + backgroundColor: Colors.amber, + colorText: Colors.black, + ); + return; + } + + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: selectedWaktuSelesai.value ?? + TimeOfDay( + hour: (selectedWaktuMulai.value!.hour + 1) % + 24, + minute: selectedWaktuMulai.value!.minute, + ), + ); + if (pickedTime != null) { + // Validasi waktu selesai harus setelah waktu mulai + if (pickedTime.hour < + selectedWaktuMulai.value!.hour || + (pickedTime.hour == + selectedWaktuMulai.value!.hour && + pickedTime.minute <= + selectedWaktuMulai.value!.minute)) { + Get.snackbar( + 'Perhatian', + 'Waktu selesai harus setelah waktu mulai', + backgroundColor: Colors.amber, + colorText: Colors.black, + ); + return; + } + + selectedWaktuSelesai.value = pickedTime; + waktuSelesaiController.text = + '${pickedTime.hour.toString().padLeft(2, '0')}:${pickedTime.minute.toString().padLeft(2, '0')}'; + } + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Waktu selesai harus dipilih'; + } + return null; + }, + ), + ], + ), ), - suffixIcon: const Icon(Icons.access_time), - ), - onTap: () async { - final TimeOfDay? pickedTime = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); - if (pickedTime != null) { - selectedTime.value = pickedTime; - waktuPenyaluranController.text = - '${pickedTime.hour.toString().padLeft(2, '0')}:${pickedTime.minute.toString().padLeft(2, '0')}'; - } - }, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Waktu penyaluran harus dipilih'; - } - return null; - }, + ], ), const SizedBox(height: 16), @@ -407,16 +561,29 @@ class TambahPenyaluranView extends GetView { child: ElevatedButton( onPressed: () { if (formKey.currentState!.validate()) { - // Gabungkan tanggal dan waktu - DateTime? tanggalWaktuPenyaluran; + // Gabungkan tanggal dan waktu mulai + DateTime? tanggalWaktuMulai; if (selectedDate.value != null && - selectedTime.value != null) { - tanggalWaktuPenyaluran = DateTime( + selectedWaktuMulai.value != null) { + tanggalWaktuMulai = DateTime( selectedDate.value!.year, selectedDate.value!.month, selectedDate.value!.day, - selectedTime.value!.hour, - selectedTime.value!.minute, + selectedWaktuMulai.value!.hour, + selectedWaktuMulai.value!.minute, + ).toLocal(); + } + + // Gabungkan tanggal dan waktu selesai + DateTime? tanggalWaktuSelesai; + if (selectedDate.value != null && + selectedWaktuSelesai.value != null) { + tanggalWaktuSelesai = DateTime( + selectedDate.value!.year, + selectedDate.value!.month, + selectedDate.value!.day, + selectedWaktuSelesai.value!.hour, + selectedWaktuSelesai.value!.minute, ).toLocal(); } @@ -427,7 +594,10 @@ class TambahPenyaluranView extends GetView { skemaId: selectedSkemaBantuanId.value!, lokasiPenyaluranId: selectedLokasiPenyaluranId.value!, jumlahPenerima: jumlahPenerima.value, - tanggalPenyaluran: tanggalWaktuPenyaluran, + tanggalPenyaluran: tanggalWaktuMulai, + tanggalWaktuSelesai: tanggalWaktuSelesai, + kategoriBantuanId: + selectedSkemaBantuan.value!.kategoriBantuanId!, ); } }, diff --git a/lib/app/services/supabase_service.dart b/lib/app/services/supabase_service.dart index 251931e..fcea23f 100644 --- a/lib/app/services/supabase_service.dart +++ b/lib/app/services/supabase_service.dart @@ -244,6 +244,8 @@ class SupabaseService extends GetxService { .lt('tanggal_penyaluran', tomorrowUtc) .inFilter('status', ['AKTIF', 'DIJADWALKAN']); + print("hari ini $response"); + return response; } catch (e) { print('Error getting jadwal hari ini: $e');