Perbarui model dan tampilan untuk mendukung fitur tanda tangan dan pembatalan penyaluran. Modifikasi PenerimaPenyaluranModel untuk menambahkan properti tandaTangan. Ubah PenyaluranBantuanModel dengan mengganti alasanPenolakan menjadi alasanPembatalan dan menambahkan tanggalPembatalan serta tanggalSelesai. Perbarui DetailPenyaluranController untuk menangani data penyaluran dan penerima dengan lebih baik. Tambahkan logika baru di DetailPenyaluranPage untuk menampilkan informasi pembatalan dan tanda tangan. Perbarui tampilan KonfirmasiPenerimaPage untuk menyertakan fitur tanda tangan saat konfirmasi penerimaan.
This commit is contained in:
@ -5,7 +5,7 @@ import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
import 'package:penyaluran_app/app/data/models/donatur_model.dart';
|
||||
import 'package:penyaluran_app/app/data/models/penitipan_bantuan_model.dart';
|
||||
import 'package:penyaluran_app/app/widgets/detail_penitipan_dialog.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:penyaluran_app/app/utils/date_formatter.dart';
|
||||
|
||||
class DetailDonaturView extends GetView<DonaturController> {
|
||||
const DetailDonaturView({super.key});
|
||||
@ -207,8 +207,7 @@ class DetailDonaturView extends GetView<DonaturController> {
|
||||
Icons.calendar_today,
|
||||
'Terdaftar Sejak',
|
||||
donatur.createdAt != null
|
||||
? DateFormat('dd MMMM yyyy', 'id_ID')
|
||||
.format(donatur.createdAt!)
|
||||
? DateFormatter.formatDate(donatur.createdAt!)
|
||||
: 'Tidak diketahui',
|
||||
),
|
||||
],
|
||||
@ -434,7 +433,7 @@ class DetailDonaturView extends GetView<DonaturController> {
|
||||
Widget _buildDonasiItem(PenitipanBantuanModel penitipan) {
|
||||
final isUang = penitipan.isUang == true;
|
||||
final tanggal = penitipan.createdAt != null
|
||||
? DateFormat('dd MMM yyyy', 'id_ID').format(penitipan.createdAt!)
|
||||
? DateFormatter.formatDate(penitipan.createdAt!, format: 'dd MMM yyyy')
|
||||
: 'Tanggal tidak diketahui';
|
||||
|
||||
String nilaiDonasi = '';
|
||||
|
@ -127,7 +127,7 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
color: AppTheme.successColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Row(
|
||||
@ -247,15 +247,15 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
||||
|
||||
switch (penerima['status']) {
|
||||
case 'Selesai':
|
||||
statusColor = Colors.green;
|
||||
statusColor = AppTheme.completedColor;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
case 'Terjadwal':
|
||||
statusColor = Colors.blue;
|
||||
statusColor = AppTheme.processedColor;
|
||||
statusIcon = Icons.event;
|
||||
break;
|
||||
case 'Belum disalurkan':
|
||||
statusColor = Colors.orange;
|
||||
statusColor = AppTheme.warningColor;
|
||||
statusIcon = Icons.pending;
|
||||
break;
|
||||
default:
|
||||
@ -412,7 +412,7 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
||||
icon: const Icon(Icons.check_circle),
|
||||
label: const Text('Konfirmasi Penyaluran'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
backgroundColor: AppTheme.successColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
|
@ -268,16 +268,16 @@ class PengaduanView extends GetView<PetugasDesaController> {
|
||||
IconData statusIcon;
|
||||
|
||||
switch (item['status']) {
|
||||
case 'Diproses':
|
||||
statusColor = Colors.orange;
|
||||
statusIcon = Icons.pending_actions;
|
||||
case 'MENUNGGU':
|
||||
statusColor = AppTheme.warningColor;
|
||||
statusIcon = Icons.pending;
|
||||
break;
|
||||
case 'Tindakan':
|
||||
statusColor = Colors.blue;
|
||||
statusIcon = Icons.engineering;
|
||||
case 'DIPROSES':
|
||||
statusColor = AppTheme.infoColor;
|
||||
statusIcon = Icons.sync;
|
||||
break;
|
||||
case 'Selesai':
|
||||
statusColor = Colors.green;
|
||||
case 'SELESAI':
|
||||
statusColor = AppTheme.successColor;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
default:
|
||||
|
@ -274,15 +274,15 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
||||
|
||||
switch (item.status) {
|
||||
case 'MENUNGGU':
|
||||
statusColor = Colors.orange;
|
||||
statusIcon = Icons.pending_actions;
|
||||
statusColor = AppTheme.warningColor;
|
||||
statusIcon = Icons.pending;
|
||||
break;
|
||||
case 'TERVERIFIKASI':
|
||||
statusColor = Colors.green;
|
||||
statusColor = AppTheme.successColor;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
case 'DITOLAK':
|
||||
statusColor = Colors.red;
|
||||
statusColor = AppTheme.errorColor;
|
||||
statusIcon = Icons.cancel;
|
||||
break;
|
||||
default:
|
||||
|
@ -73,11 +73,6 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Ringkasan Permintaan Penjadwalan
|
||||
PermintaanPenjadwalanSummaryWidget(controller: controller),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Jadwal hari ini
|
||||
JadwalSectionWidget(
|
||||
controller: controller,
|
||||
@ -96,15 +91,7 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
status: 'Terjadwal',
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Jadwal selesai
|
||||
JadwalSectionWidget(
|
||||
controller: controller,
|
||||
title: 'Terlaksana',
|
||||
jadwalList: controller.jadwalTerlaksana,
|
||||
status: 'Terlaksana',
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
);
|
||||
}),
|
||||
|
@ -5,7 +5,7 @@ import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penya
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
||||
const PermintaanPenjadwalanView({Key? key}) : super(key: key);
|
||||
const PermintaanPenjadwalanView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -239,7 +239,8 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
||||
|
||||
Widget _buildPermintaanItem(
|
||||
BuildContext context, PenyaluranBantuanModel item) {
|
||||
Color statusColor = Colors.orange;
|
||||
// Status selalu MENUNGGU untuk permintaan penjadwalan
|
||||
Color statusColor = AppTheme.warningColor;
|
||||
IconData statusIcon = Icons.pending_actions;
|
||||
|
||||
return Container(
|
||||
|
@ -109,6 +109,22 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
);
|
||||
}
|
||||
|
||||
// Tampilkan tombol riwayat jika tab Penyaluran aktif
|
||||
if (activeTab == 1) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Get.toNamed('/petugas-desa/riwayat-penyaluran');
|
||||
},
|
||||
icon: const Icon(Icons.history),
|
||||
tooltip: 'Riwayat Penyaluran',
|
||||
),
|
||||
notificationButton,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return notificationButton;
|
||||
}),
|
||||
],
|
||||
|
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/data/models/penitipan_bantuan_model.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart';
|
||||
import 'package:penyaluran_app/app/utils/date_formatter.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class RiwayatPenitipanView extends GetView<PenitipanBantuanController> {
|
||||
const RiwayatPenitipanView({super.key});
|
||||
@ -186,11 +187,11 @@ class RiwayatPenitipanView extends GetView<PenitipanBantuanController> {
|
||||
|
||||
switch (item.status) {
|
||||
case 'TERVERIFIKASI':
|
||||
statusColor = Colors.green;
|
||||
statusColor = AppTheme.successColor;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
case 'DITOLAK':
|
||||
statusColor = Colors.red;
|
||||
statusColor = AppTheme.errorColor;
|
||||
statusIcon = Icons.cancel;
|
||||
break;
|
||||
default:
|
||||
|
392
lib/app/modules/petugas_desa/views/riwayat_penyaluran_view.dart
Normal file
392
lib/app/modules/petugas_desa/views/riwayat_penyaluran_view.dart
Normal file
@ -0,0 +1,392 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||
import 'package:penyaluran_app/app/utils/date_formatter.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class RiwayatPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
const RiwayatPenyaluranView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Riwayat Penyaluran'),
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(text: 'Terlaksana'),
|
||||
Tab(text: 'Batal Terlaksana'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Obx(() => TabBarView(
|
||||
children: [
|
||||
// Tab Terlaksana
|
||||
_buildPenyaluranList(context, 'TERLAKSANA'),
|
||||
// Tab Batal Terlaksana
|
||||
_buildPenyaluranList(context, 'BATALTERLAKSANA'),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPenyaluranList(BuildContext context, String status) {
|
||||
var filteredList = controller.jadwalTerlaksana
|
||||
.where((item) => item.status == status)
|
||||
.toList();
|
||||
|
||||
// Filter berdasarkan pencarian jika ada teks pencarian
|
||||
final searchText = controller.searchController.text.toLowerCase();
|
||||
if (searchText.isNotEmpty) {
|
||||
filteredList = filteredList.where((item) {
|
||||
final nama = item.nama?.toLowerCase() ?? '';
|
||||
final deskripsi = item.deskripsi?.toLowerCase() ?? '';
|
||||
final lokasiNama = controller
|
||||
.getLokasiPenyaluranName(item.lokasiPenyaluranId)
|
||||
.toLowerCase();
|
||||
final kategoriNama = controller
|
||||
.getKategoriBantuanName(item.kategoriBantuanId)
|
||||
.toLowerCase();
|
||||
final tanggal =
|
||||
DateFormatter.formatDateTime(item.tanggalPenyaluran).toLowerCase();
|
||||
|
||||
return nama.contains(searchText) ||
|
||||
deskripsi.contains(searchText) ||
|
||||
lokasiNama.contains(searchText) ||
|
||||
kategoriNama.contains(searchText) ||
|
||||
tanggal.contains(searchText);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: controller.refreshData,
|
||||
child: controller.isLoading.value
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Search field
|
||||
TextField(
|
||||
controller: controller.searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Cari riwayat penyaluran...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey.shade100,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
// Trigger rebuild dengan Obx
|
||||
controller.update();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Info jumlah item
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Daftar Penyaluran ${status == 'TERLAKSANA' ? 'terlaksana' : 'batal terlaksana'}',
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${DateFormatter.formatNumber(filteredList.length)} item',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Informasi jumlah item dan terakhir update
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Total: ${DateFormatter.formatNumber(filteredList.length)} item',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
// Informasi terakhir update
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.update,
|
||||
size: 16, color: Colors.grey[600]),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Update: ${DateFormatter.formatDateTimeWithHour(DateTime.now())}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Daftar penyaluran
|
||||
filteredList.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.inbox_outlined,
|
||||
size: 80,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Tidak ada data penyaluran ${status == 'TERLAKSANA' ? 'terlaksana' : 'batal terlaksana'}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: filteredList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildPenyaluranItem(
|
||||
context, filteredList[index]);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPenyaluranItem(
|
||||
BuildContext context, PenyaluranBantuanModel item) {
|
||||
Color statusColor;
|
||||
IconData statusIcon;
|
||||
|
||||
switch (item.status) {
|
||||
case 'TERLAKSANA':
|
||||
statusColor = AppTheme.completedColor;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
case 'BATALTERLAKSANA':
|
||||
statusColor = AppTheme.errorColor;
|
||||
statusIcon = Icons.cancel;
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.grey;
|
||||
statusIcon = Icons.help_outline;
|
||||
}
|
||||
|
||||
final lokasiNama =
|
||||
controller.getLokasiPenyaluranName(item.lokasiPenyaluranId);
|
||||
final kategoriNama =
|
||||
controller.getKategoriBantuanName(item.kategoriBantuanId);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(
|
||||
color: statusColor.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () {
|
||||
Get.toNamed('/detail-penyaluran', arguments: item);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.nama ?? 'Penyaluran tanpa nama',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
statusIcon,
|
||||
size: 16,
|
||||
color: statusColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
item.status == 'TERLAKSANA' ? 'Terlaksana' : 'Batal',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (item.deskripsi != null && item.deskripsi!.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
item.deskripsi!,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
const Divider(height: 24),
|
||||
_buildInfoItem(
|
||||
Icons.location_on_outlined,
|
||||
'Lokasi',
|
||||
lokasiNama,
|
||||
Theme.of(context).textTheme,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildInfoItem(
|
||||
Icons.category_outlined,
|
||||
'Kategori',
|
||||
kategoriNama,
|
||||
Theme.of(context).textTheme,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildInfoItem(
|
||||
Icons.event,
|
||||
'Tanggal',
|
||||
DateFormatter.formatDateTime(item.tanggalPenyaluran,
|
||||
defaultValue: 'Tidak ada tanggal'),
|
||||
Theme.of(context).textTheme,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoItem(
|
||||
Icons.people_outline,
|
||||
'Jumlah Penerima',
|
||||
'${DateFormatter.formatNumber(item.jumlahPenerima ?? 0)} orang',
|
||||
Theme.of(context).textTheme,
|
||||
),
|
||||
if (item.alasanPembatalan != null &&
|
||||
item.alasanPembatalan!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoItem(
|
||||
Icons.info_outline,
|
||||
'Alasan Pembatalan',
|
||||
item.alasanPembatalan!,
|
||||
Theme.of(context).textTheme,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
Get.toNamed('/detail-penyaluran', arguments: item);
|
||||
},
|
||||
icon: const Icon(Icons.visibility_outlined),
|
||||
label: const Text('Lihat Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: statusColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoItem(
|
||||
IconData icon,
|
||||
String label,
|
||||
String value,
|
||||
TextTheme textTheme,
|
||||
) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
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/utils/date_formatter.dart';
|
||||
|
||||
class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
const TambahPenyaluranView({super.key});
|
||||
@ -27,8 +27,6 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
final TextEditingController tanggalPenyaluranController =
|
||||
TextEditingController();
|
||||
final TextEditingController waktuMulaiController = TextEditingController();
|
||||
final TextEditingController waktuSelesaiController =
|
||||
TextEditingController();
|
||||
|
||||
// Variabel untuk menyimpan nilai yang dipilih
|
||||
final Rx<String?> selectedSkemaBantuanId = Rx<String?>(null);
|
||||
@ -40,7 +38,6 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
// Tanggal dan waktu penyaluran
|
||||
final Rx<DateTime?> selectedDate = Rx<DateTime?>(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 {
|
||||
@ -209,7 +206,7 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 8),
|
||||
Obx(() => jumlahPenerima.value > 0
|
||||
? TextButton.icon(
|
||||
onPressed: () async {
|
||||
@ -295,7 +292,7 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
label: const Text('Lihat Daftar Penerima'),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Tanggal Penyaluran
|
||||
Text(
|
||||
@ -330,7 +327,7 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
if (pickedDate != null) {
|
||||
selectedDate.value = pickedDate;
|
||||
tanggalPenyaluranController.text =
|
||||
DateFormat('dd MMMM yyyy', 'id_ID').format(pickedDate);
|
||||
DateFormatter.formatDate(pickedDate);
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
@ -372,152 +369,43 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Rentang Waktu Penyaluran
|
||||
Text(
|
||||
'Rentang Waktu Penyaluran',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Waktu Mulai
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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;
|
||||
},
|
||||
),
|
||||
],
|
||||
const Text('Waktu Mulai'),
|
||||
const SizedBox(height: 4),
|
||||
TextFormField(
|
||||
controller: waktuMulaiController,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Pilih waktu 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')}';
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Waktu mulai harus dipilih';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -570,19 +458,6 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
).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();
|
||||
}
|
||||
|
||||
// Panggil fungsi untuk menambahkan penyaluran
|
||||
controller.tambahPenyaluran(
|
||||
nama: namaController.text,
|
||||
@ -591,7 +466,6 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
lokasiPenyaluranId: selectedLokasiPenyaluranId.value!,
|
||||
jumlahPenerima: jumlahPenerima.value,
|
||||
tanggalPenyaluran: tanggalWaktuMulai,
|
||||
tanggalWaktuSelesai: tanggalWaktuSelesai,
|
||||
kategoriBantuanId:
|
||||
selectedSkemaBantuan.value!.kategoriBantuanId!,
|
||||
);
|
||||
|
Reference in New Issue
Block a user