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.

This commit is contained in:
Khafidh Fuadi
2025-03-15 00:03:00 +07:00
parent 0e757c0b94
commit 675b0a7cad
4 changed files with 242 additions and 58 deletions

View File

@ -89,17 +89,14 @@ class JadwalPenyaluranController extends GetxController {
// Memeriksa dan memperbarui status jadwal
Future<void> 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<PenyaluranBantuanModel> jadwalToUpdate = [];
List<PenyaluranBantuanModel> 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

View File

@ -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<JadwalPenyaluranController> {
const TambahPenyaluranView({super.key});
@ -27,7 +26,8 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
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<JadwalPenyaluranController> {
// Tanggal dan waktu penyaluran
final Rx<DateTime?> selectedDate = Rx<DateTime?>(null);
final Rx<TimeOfDay?> selectedTime = Rx<TimeOfDay?>(null);
final Rx<TimeOfDay?> selectedWaktuMulai = Rx<TimeOfDay?>(null);
final Rx<TimeOfDay?> selectedWaktuSelesai = Rx<TimeOfDay?>(null);
// Fungsi untuk memuat data pengajuan kelayakan bantuan
Future<void> loadPengajuanKelayakan(String skemaId) async {
@ -200,6 +201,15 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
),
)),
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<JadwalPenyaluranController> {
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<JadwalPenyaluranController> {
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<JadwalPenyaluranController> {
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<JadwalPenyaluranController> {
skemaId: selectedSkemaBantuanId.value!,
lokasiPenyaluranId: selectedLokasiPenyaluranId.value!,
jumlahPenerima: jumlahPenerima.value,
tanggalPenyaluran: tanggalWaktuPenyaluran,
tanggalPenyaluran: tanggalWaktuMulai,
tanggalWaktuSelesai: tanggalWaktuSelesai,
kategoriBantuanId:
selectedSkemaBantuan.value!.kategoriBantuanId!,
);
}
},