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

@ -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!,
);
}
},