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');