Perbarui dependensi dan konfigurasi lokal untuk mendukung fitur baru
- Tambahkan dependensi baru: syncfusion_flutter_calendar, syncfusion_localizations, dan flutter_localizations di pubspec.yaml - Perbarui konfigurasi lokal di main.dart untuk mendukung bahasa Indonesia dan menambahkan delegasi lokal - Modifikasi model PenyaluranBantuan untuk memastikan format tanggal menggunakan UTC - Perbarui tampilan dan logika di beberapa widget untuk meningkatkan pengalaman pengguna dan konsistensi data - Ganti posisi snack bar dari bawah ke atas untuk notifikasi yang lebih baik
This commit is contained in:
67
lib/app/data/models/penerima_penyaluran_model.dart
Normal file
67
lib/app/data/models/penerima_penyaluran_model.dart
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class PenerimaPenyaluranModel {
|
||||||
|
final int? id;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final String? penyaluranBantuanId;
|
||||||
|
final String? wargaId;
|
||||||
|
final String? statusPenerimaan;
|
||||||
|
final DateTime? tanggalPenerimaan;
|
||||||
|
final String? buktiPenerimaan;
|
||||||
|
final String? keterangan;
|
||||||
|
final double? jumlahBantuan;
|
||||||
|
final String? stokBantuanId;
|
||||||
|
final Map<String, dynamic>? warga; // Data warga yang terkait
|
||||||
|
|
||||||
|
PenerimaPenyaluranModel({
|
||||||
|
this.id,
|
||||||
|
this.createdAt,
|
||||||
|
this.penyaluranBantuanId,
|
||||||
|
this.wargaId,
|
||||||
|
this.statusPenerimaan,
|
||||||
|
this.tanggalPenerimaan,
|
||||||
|
this.buktiPenerimaan,
|
||||||
|
this.keterangan,
|
||||||
|
this.jumlahBantuan,
|
||||||
|
this.stokBantuanId,
|
||||||
|
this.warga,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PenerimaPenyaluranModel.fromRawJson(String str) =>
|
||||||
|
PenerimaPenyaluranModel.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory PenerimaPenyaluranModel.fromJson(Map<String, dynamic> json) =>
|
||||||
|
PenerimaPenyaluranModel(
|
||||||
|
id: json["id"],
|
||||||
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
penyaluranBantuanId: json["penyaluran_bantuan_id"],
|
||||||
|
wargaId: json["warga_id"],
|
||||||
|
statusPenerimaan: json["status_penerimaan"],
|
||||||
|
tanggalPenerimaan: json["tanggal_penerimaan"] != null
|
||||||
|
? DateTime.parse(json["tanggal_penerimaan"])
|
||||||
|
: null,
|
||||||
|
buktiPenerimaan: json["bukti_penerimaan"],
|
||||||
|
keterangan: json["keterangan"],
|
||||||
|
jumlahBantuan: json["jumlah_bantuan"]?.toDouble(),
|
||||||
|
stokBantuanId: json["stok_bantuan_id"],
|
||||||
|
warga: json["warga"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"id": id,
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
|
"penyaluran_bantuan_id": penyaluranBantuanId,
|
||||||
|
"warga_id": wargaId,
|
||||||
|
"status_penerimaan": statusPenerimaan,
|
||||||
|
"tanggal_penerimaan": tanggalPenerimaan?.toIso8601String(),
|
||||||
|
"bukti_penerimaan": buktiPenerimaan,
|
||||||
|
"keterangan": keterangan,
|
||||||
|
"jumlah_bantuan": jumlahBantuan,
|
||||||
|
"stok_bantuan_id": stokBantuanId,
|
||||||
|
"warga": warga,
|
||||||
|
};
|
||||||
|
}
|
@ -50,22 +50,22 @@ class PenyaluranBantuanModel {
|
|||||||
status: json["status"],
|
status: json["status"],
|
||||||
alasanPenolakan: json["alasan_penolakan"],
|
alasanPenolakan: json["alasan_penolakan"],
|
||||||
tanggalPenjadwalan: json["tanggal_penjadwalan"] != null
|
tanggalPenjadwalan: json["tanggal_penjadwalan"] != null
|
||||||
? DateTime.parse(json["tanggal_penjadwalan"])
|
? DateTime.parse(json["tanggal_penjadwalan"]).toUtc()
|
||||||
: null,
|
: null,
|
||||||
tanggalPenyaluran: json["tanggal_penyaluran"] != null
|
tanggalPenyaluran: json["tanggal_penyaluran"] != null
|
||||||
? DateTime.parse(json["tanggal_penyaluran"])
|
? DateTime.parse(json["tanggal_penyaluran"]).toUtc()
|
||||||
: null,
|
: null,
|
||||||
kategoriBantuanId: json["kategori_bantuan_id"],
|
kategoriBantuanId: json["kategori_bantuan_id"],
|
||||||
tanggalPermintaan: json["tanggal_permintaan"] != null
|
tanggalPermintaan: json["tanggal_permintaan"] != null
|
||||||
? DateTime.parse(json["tanggal_permintaan"])
|
? DateTime.parse(json["tanggal_permintaan"]).toUtc()
|
||||||
: null,
|
: null,
|
||||||
jumlahPenerima: json["jumlah_penerima"],
|
jumlahPenerima: json["jumlah_penerima"],
|
||||||
skemaId: json["skema_id"],
|
skemaId: json["skema_id"],
|
||||||
createdAt: json["created_at"] != null
|
createdAt: json["created_at"] != null
|
||||||
? DateTime.parse(json["created_at"])
|
? DateTime.parse(json["created_at"]).toUtc()
|
||||||
: null,
|
: null,
|
||||||
updatedAt: json["updated_at"] != null
|
updatedAt: json["updated_at"] != null
|
||||||
? DateTime.parse(json["updated_at"])
|
? DateTime.parse(json["updated_at"]).toUtc()
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -77,13 +77,13 @@ class PenyaluranBantuanModel {
|
|||||||
"petugas_id": petugasId,
|
"petugas_id": petugasId,
|
||||||
"status": status,
|
"status": status,
|
||||||
"alasan_penolakan": alasanPenolakan,
|
"alasan_penolakan": alasanPenolakan,
|
||||||
"tanggal_penjadwalan": tanggalPenjadwalan?.toIso8601String(),
|
"tanggal_penjadwalan": tanggalPenjadwalan?.toUtc().toIso8601String(),
|
||||||
"tanggal_penyaluran": tanggalPenyaluran?.toIso8601String(),
|
"tanggal_penyaluran": tanggalPenyaluran?.toUtc().toIso8601String(),
|
||||||
"kategori_bantuan_id": kategoriBantuanId,
|
"kategori_bantuan_id": kategoriBantuanId,
|
||||||
"tanggal_permintaan": tanggalPermintaan?.toIso8601String(),
|
"tanggal_permintaan": tanggalPermintaan?.toUtc().toIso8601String(),
|
||||||
"jumlah_penerima": jumlahPenerima,
|
"jumlah_penerima": jumlahPenerima,
|
||||||
"skema_id": skemaId,
|
"skema_id": skemaId,
|
||||||
"created_at": createdAt?.toIso8601String(),
|
"created_at": createdAt?.toUtc().toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toUtc().toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ class AuthController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Terjadi kesalahan pada form login. Silakan coba lagi.',
|
'Terjadi kesalahan pada form login. Silakan coba lagi.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -252,7 +252,7 @@ class AuthController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Login gagal: ${e.toString()}',
|
'Login gagal: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -278,7 +278,7 @@ class AuthController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Logout gagal: ${e.toString()}',
|
'Logout gagal: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,524 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:syncfusion_flutter_calendar/calendar.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/theme/app_theme.dart';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||||
|
|
||||||
|
class CalendarViewWidget extends StatelessWidget {
|
||||||
|
final JadwalPenyaluranController controller;
|
||||||
|
|
||||||
|
const CalendarViewWidget({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
'Kalender Penyaluran Bulan Ini',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 500,
|
||||||
|
child: Obx(() {
|
||||||
|
return SfCalendar(
|
||||||
|
view: CalendarView.month,
|
||||||
|
dataSource: _getCalendarDataSource(),
|
||||||
|
timeZone: 'Asia/Jakarta',
|
||||||
|
monthViewSettings: MonthViewSettings(
|
||||||
|
appointmentDisplayMode: MonthAppointmentDisplayMode.indicator,
|
||||||
|
showAgenda: true,
|
||||||
|
agendaViewHeight: 250,
|
||||||
|
agendaItemHeight: 70,
|
||||||
|
dayFormat: 'EEE',
|
||||||
|
numberOfWeeksInView: 6,
|
||||||
|
appointmentDisplayCount: 3,
|
||||||
|
monthCellStyle: MonthCellStyle(
|
||||||
|
textStyle: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
trailingDatesTextStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
leadingDatesTextStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
agendaStyle: const AgendaStyle(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
appointmentTextStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
dateTextStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
dayTextStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cellBorderColor: Colors.grey.withOpacity(0.2),
|
||||||
|
todayHighlightColor: AppTheme.primaryColor,
|
||||||
|
selectionDecoration: BoxDecoration(
|
||||||
|
color: Colors.transparent,
|
||||||
|
border: Border.all(color: AppTheme.primaryColor, width: 2),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
shape: BoxShape.rectangle,
|
||||||
|
),
|
||||||
|
headerStyle: const CalendarHeaderStyle(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
monthCellBuilder: _monthCellBuilder,
|
||||||
|
onTap: (CalendarTapDetails details) {
|
||||||
|
if (details.targetElement == CalendarElement.appointment) {
|
||||||
|
final Appointment appointment = details.appointments![0];
|
||||||
|
_showAppointmentDetails(context, appointment);
|
||||||
|
} else if (details.targetElement ==
|
||||||
|
CalendarElement.calendarCell) {
|
||||||
|
final List<Appointment> appointmentsOnDay =
|
||||||
|
_getAppointmentsOnDay(details.date!);
|
||||||
|
// if (appointmentsOnDay.isNotEmpty) {
|
||||||
|
// _showAppointmentsOnDay(
|
||||||
|
// context, details.date!, appointmentsOnDay);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _monthCellBuilder(BuildContext context, MonthCellDetails details) {
|
||||||
|
final String dayName = _getDayName(details.date.weekday);
|
||||||
|
|
||||||
|
final bool hasAppointments = _hasAppointmentsOnDay(details.date);
|
||||||
|
|
||||||
|
Color textColor;
|
||||||
|
if (details.date.month != DateTime.now().month) {
|
||||||
|
textColor = Colors.grey.withOpacity(0.7);
|
||||||
|
} else if (details.date.day == DateTime.now().day &&
|
||||||
|
details.date.month == DateTime.now().month &&
|
||||||
|
details.date.year == DateTime.now().year) {
|
||||||
|
textColor = Colors.white;
|
||||||
|
} else {
|
||||||
|
textColor = Colors.black87;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
decoration: details.date.day == DateTime.now().day &&
|
||||||
|
details.date.month == DateTime.now().month &&
|
||||||
|
details.date.year == DateTime.now().year
|
||||||
|
? BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (details.date.day <= 7)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2),
|
||||||
|
child: Text(
|
||||||
|
dayName,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: details.date.day == DateTime.now().day &&
|
||||||
|
details.date.month == DateTime.now().month &&
|
||||||
|
details.date.year == DateTime.now().year
|
||||||
|
? Colors.white
|
||||||
|
: AppTheme.primaryColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
details.date.day.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: textColor,
|
||||||
|
fontWeight: details.date.day == DateTime.now().day &&
|
||||||
|
details.date.month == DateTime.now().month &&
|
||||||
|
details.date.year == DateTime.now().year
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (hasAppointments && details.appointments.isEmpty)
|
||||||
|
Container(
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
margin: const EdgeInsets.only(bottom: 2),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getDayName(int weekday) {
|
||||||
|
switch (weekday) {
|
||||||
|
case DateTime.monday:
|
||||||
|
return 'Sen';
|
||||||
|
case DateTime.tuesday:
|
||||||
|
return 'Sel';
|
||||||
|
case DateTime.wednesday:
|
||||||
|
return 'Rab';
|
||||||
|
case DateTime.thursday:
|
||||||
|
return 'Kam';
|
||||||
|
case DateTime.friday:
|
||||||
|
return 'Jum';
|
||||||
|
case DateTime.saturday:
|
||||||
|
return 'Sab';
|
||||||
|
case DateTime.sunday:
|
||||||
|
return 'Min';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _hasAppointmentsOnDay(DateTime date) {
|
||||||
|
final _AppointmentDataSource dataSource = _getCalendarDataSource();
|
||||||
|
|
||||||
|
for (final appointment in dataSource.appointments!) {
|
||||||
|
final Appointment app = appointment as Appointment;
|
||||||
|
if (app.startTime.year == date.year &&
|
||||||
|
app.startTime.month == date.month &&
|
||||||
|
app.startTime.day == date.day) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_AppointmentDataSource _getCalendarDataSource() {
|
||||||
|
List<Appointment> appointments = [];
|
||||||
|
|
||||||
|
List<PenyaluranBantuanModel> allJadwal = [
|
||||||
|
...controller.jadwalHariIni,
|
||||||
|
...controller.jadwalMendatang,
|
||||||
|
...controller.jadwalSelesai,
|
||||||
|
];
|
||||||
|
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
DateTime firstDayOfMonth = DateTime(now.year, now.month, 1);
|
||||||
|
DateTime lastDayOfMonth = DateTime(now.year, now.month + 1, 0);
|
||||||
|
|
||||||
|
for (var jadwal in allJadwal) {
|
||||||
|
if (jadwal.tanggalPenyaluran != null) {
|
||||||
|
DateTime jadwalDate =
|
||||||
|
DateTimeHelper.toLocalDateTime(jadwal.tanggalPenyaluran!);
|
||||||
|
|
||||||
|
if (jadwalDate
|
||||||
|
.isAfter(firstDayOfMonth.subtract(const Duration(days: 1))) &&
|
||||||
|
jadwalDate.isBefore(lastDayOfMonth.add(const Duration(days: 1)))) {
|
||||||
|
Color appointmentColor;
|
||||||
|
|
||||||
|
// Periksa status jadwal
|
||||||
|
if (jadwal.status == 'SELESAI') {
|
||||||
|
appointmentColor = Colors.grey;
|
||||||
|
} else if (jadwal.status == 'BERLANGSUNG') {
|
||||||
|
appointmentColor = Colors.green;
|
||||||
|
} else if (jadwal.status == 'DIJADWALKAN' ||
|
||||||
|
jadwal.status == 'DISETUJUI') {
|
||||||
|
appointmentColor = AppTheme.primaryColor;
|
||||||
|
} else {
|
||||||
|
appointmentColor = Colors.orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
appointments.add(
|
||||||
|
Appointment(
|
||||||
|
startTime: jadwalDate,
|
||||||
|
endTime: jadwalDate.add(const Duration(hours: 2)),
|
||||||
|
subject: jadwal.nama ?? 'Penyaluran Bantuan',
|
||||||
|
color: appointmentColor,
|
||||||
|
notes: jadwal.deskripsi,
|
||||||
|
location:
|
||||||
|
controller.getLokasiPenyaluranName(jadwal.lokasiPenyaluranId),
|
||||||
|
recurrenceRule: '',
|
||||||
|
id: jadwal.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _AppointmentDataSource(appointments);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Appointment> _getAppointmentsOnDay(DateTime date) {
|
||||||
|
final List<Appointment> appointments = [];
|
||||||
|
final _AppointmentDataSource dataSource = _getCalendarDataSource();
|
||||||
|
|
||||||
|
for (final appointment in dataSource.appointments!) {
|
||||||
|
final Appointment app = appointment as Appointment;
|
||||||
|
if (app.startTime.year == date.year &&
|
||||||
|
app.startTime.month == date.month &&
|
||||||
|
app.startTime.day == date.day) {
|
||||||
|
appointments.add(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return appointments;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showAppointmentsOnDay(
|
||||||
|
BuildContext context, DateTime date, List<Appointment> appointments) {
|
||||||
|
final String formattedDate = DateTimeHelper.formatDateIndonesian(date);
|
||||||
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
builder: (context) => Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Jadwal Penyaluran $formattedDate',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (appointments.isEmpty)
|
||||||
|
const Text('Tidak ada jadwal penyaluran pada tanggal ini.')
|
||||||
|
else
|
||||||
|
ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: appointments.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final appointment = appointments[index];
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(12),
|
||||||
|
leading: Container(
|
||||||
|
width: 12,
|
||||||
|
height: double.infinity,
|
||||||
|
color: appointment.color,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
appointment.subject,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.access_time, size: 14),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'${appointment.startTime.hour}:${appointment.startTime.minute.toString().padLeft(2, '0')} - ${appointment.endTime.hour}:${appointment.endTime.minute.toString().padLeft(2, '0')} WIB',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.location_on, size: 14),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Text(appointment.location ?? ''),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => Get.toNamed('/pelaksanaan-penyaluran',
|
||||||
|
arguments: appointment),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Tutup'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showAppointmentDetails(BuildContext context, Appointment appointment) {
|
||||||
|
final String formattedDate =
|
||||||
|
DateTimeHelper.formatDateIndonesian(appointment.startTime);
|
||||||
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
builder: (context) => Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
appointment.subject,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.calendar_today, size: 16),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
formattedDate,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.access_time, size: 16),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'${appointment.startTime.hour}:${appointment.startTime.minute.toString().padLeft(2, '0')} - ${appointment.endTime.hour}:${appointment.endTime.minute.toString().padLeft(2, '0')} WIB',
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (appointment.location != null &&
|
||||||
|
appointment.location!.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.location_on, size: 16),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
appointment.location!,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (appointment.notes != null && appointment.notes!.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Deskripsi:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
appointment.notes!,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Tutup'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDateIndonesian(DateTime date) {
|
||||||
|
return DateTimeHelper.formatDateIndonesian(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppointmentDataSource extends CalendarDataSource {
|
||||||
|
_AppointmentDataSource(List<Appointment> source) {
|
||||||
|
appointments = source;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ 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/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||||
|
|
||||||
class JadwalSectionWidget extends StatelessWidget {
|
class JadwalSectionWidget extends StatelessWidget {
|
||||||
final JadwalPenyaluranController controller;
|
final JadwalPenyaluranController controller;
|
||||||
@ -88,11 +89,11 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
IconData _getStatusIcon() {
|
IconData _getStatusIcon() {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'Aktif':
|
case 'Aktif':
|
||||||
return Icons.event_available;
|
return Icons.event_note;
|
||||||
case 'Terjadwal':
|
case 'Terjadwal':
|
||||||
return Icons.pending_actions;
|
return Icons.pending_actions;
|
||||||
case 'Selesai':
|
case 'Selesai':
|
||||||
return Icons.event_busy;
|
return Icons.event_available;
|
||||||
default:
|
default:
|
||||||
return Icons.event_note;
|
return Icons.event_note;
|
||||||
}
|
}
|
||||||
@ -124,6 +125,40 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getStatusText(PenyaluranBantuanModel jadwal) {
|
||||||
|
// Jika status jadwal adalah BERLANGSUNG, tampilkan sebagai "Aktif"
|
||||||
|
if (jadwal.status == 'BERLANGSUNG') {
|
||||||
|
return 'Aktif';
|
||||||
|
}
|
||||||
|
// Jika status jadwal adalah DIJADWALKAN, tampilkan sebagai "Terjadwal"
|
||||||
|
else if (jadwal.status == 'DIJADWALKAN' || jadwal.status == 'DISETUJUI') {
|
||||||
|
return 'Terjadwal';
|
||||||
|
}
|
||||||
|
// Jika status jadwal adalah SELESAI, tampilkan sebagai "Selesai"
|
||||||
|
else if (jadwal.status == 'SELESAI') {
|
||||||
|
return 'Selesai';
|
||||||
|
}
|
||||||
|
// Default status
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getStatusColorByJadwal(PenyaluranBantuanModel jadwal) {
|
||||||
|
// Jika status jadwal adalah BERLANGSUNG, gunakan warna hijau
|
||||||
|
if (jadwal.status == 'BERLANGSUNG') {
|
||||||
|
return Colors.green;
|
||||||
|
}
|
||||||
|
// Jika status jadwal adalah DIJADWALKAN, gunakan warna biru
|
||||||
|
else if (jadwal.status == 'DIJADWALKAN' || jadwal.status == 'DISETUJUI') {
|
||||||
|
return Colors.blue;
|
||||||
|
}
|
||||||
|
// Jika status jadwal adalah SELESAI, gunakan warna abu-abu
|
||||||
|
else if (jadwal.status == 'SELESAI') {
|
||||||
|
return Colors.grey;
|
||||||
|
}
|
||||||
|
// Default warna
|
||||||
|
return _getStatusColor();
|
||||||
|
}
|
||||||
|
|
||||||
List<PenyaluranBantuanModel> _getCurrentJadwalList() {
|
List<PenyaluranBantuanModel> _getCurrentJadwalList() {
|
||||||
switch (title) {
|
switch (title) {
|
||||||
case 'Hari Ini':
|
case 'Hari Ini':
|
||||||
@ -138,12 +173,12 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildJadwalItem(TextTheme textTheme, PenyaluranBantuanModel jadwal) {
|
Widget _buildJadwalItem(TextTheme textTheme, PenyaluranBantuanModel jadwal) {
|
||||||
Color statusColor = _getStatusColor();
|
Color statusColor = _getStatusColorByJadwal(jadwal);
|
||||||
|
String statusText = _getStatusText(jadwal);
|
||||||
|
|
||||||
// Format tanggal dan waktu
|
// Format tanggal dan waktu menggunakan helper
|
||||||
String formattedDateTime = jadwal.tanggalPenyaluran != null
|
String formattedDateTime =
|
||||||
? "${DateFormat('dd MMM yyyy').format(jadwal.tanggalPenyaluran!)} ${DateFormat('HH:mm').format(jadwal.tanggalPenyaluran!)}"
|
DateTimeHelper.formatDateTime(jadwal.tanggalPenyaluran);
|
||||||
: 'Belum ditentukan';
|
|
||||||
|
|
||||||
// Dapatkan nama lokasi dan kategori
|
// Dapatkan nama lokasi dan kategori
|
||||||
String lokasiName =
|
String lokasiName =
|
||||||
@ -164,7 +199,24 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwal);
|
// Konversi PenyaluranBantuanModel ke Map<String, dynamic>
|
||||||
|
final jadwalMap = {
|
||||||
|
'id': jadwal.id,
|
||||||
|
'nama': jadwal.nama,
|
||||||
|
'deskripsi': jadwal.deskripsi,
|
||||||
|
'lokasi': jadwal.nama, // Gunakan nama sebagai lokasi
|
||||||
|
'kategori_bantuan': jadwal.kategoriBantuanId,
|
||||||
|
'tanggal': jadwal.tanggalPenyaluran != null
|
||||||
|
? DateTimeHelper.formatDate(jadwal.tanggalPenyaluran)
|
||||||
|
: '-',
|
||||||
|
'waktu': jadwal.tanggalPenyaluran != null
|
||||||
|
? DateTimeHelper.formatTime(jadwal.tanggalPenyaluran)
|
||||||
|
: '-',
|
||||||
|
'jumlah_penerima': jadwal.jumlahPenerima,
|
||||||
|
'status': jadwal.status,
|
||||||
|
};
|
||||||
|
|
||||||
|
Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwalMap);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@ -213,7 +265,7 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
status,
|
statusText,
|
||||||
style: textTheme.bodySmall?.copyWith(
|
style: textTheme.bodySmall?.copyWith(
|
||||||
color: statusColor,
|
color: statusColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -279,8 +331,25 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
// Konversi PenyaluranBantuanModel ke Map<String, dynamic>
|
||||||
|
final jadwalMap = {
|
||||||
|
'id': jadwal.id,
|
||||||
|
'nama': jadwal.nama,
|
||||||
|
'deskripsi': jadwal.deskripsi,
|
||||||
|
'lokasi': jadwal.nama, // Gunakan nama sebagai lokasi
|
||||||
|
'kategori_bantuan': jadwal.kategoriBantuanId,
|
||||||
|
'tanggal': jadwal.tanggalPenyaluran != null
|
||||||
|
? DateTimeHelper.formatDate(jadwal.tanggalPenyaluran)
|
||||||
|
: '-',
|
||||||
|
'waktu': jadwal.tanggalPenyaluran != null
|
||||||
|
? DateTimeHelper.formatTime(jadwal.tanggalPenyaluran)
|
||||||
|
: '-',
|
||||||
|
'jumlah_penerima': jadwal.jumlahPenerima,
|
||||||
|
'status': jadwal.status,
|
||||||
|
};
|
||||||
|
|
||||||
Get.toNamed(Routes.pelaksanaanPenyaluran,
|
Get.toNamed(Routes.pelaksanaanPenyaluran,
|
||||||
arguments: jadwal);
|
arguments: jadwalMap);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.info_outline, size: 16),
|
icon: const Icon(Icons.info_outline, size: 16),
|
||||||
label: const Text('Lihat Detail'),
|
label: const Text('Lihat Detail'),
|
||||||
|
@ -256,7 +256,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
'Permintaan penjadwalan berhasil dikonfirmasi',
|
'Permintaan penjadwalan berhasil dikonfirmasi',
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
@ -264,7 +264,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
'Silakan pilih jadwal penyaluran terlebih dahulu',
|
'Silakan pilih jadwal penyaluran terlebih dahulu',
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -324,7 +324,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
'Permintaan penjadwalan berhasil ditolak',
|
'Permintaan penjadwalan berhasil ditolak',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
@ -332,7 +332,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
'Silakan masukkan alasan penolakan',
|
'Silakan masukkan alasan penolakan',
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,8 @@ import 'package:penyaluran_app/app/data/models/kategori_bantuan_model.dart';
|
|||||||
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
class JadwalPenyaluranController extends GetxController {
|
class JadwalPenyaluranController extends GetxController {
|
||||||
final AuthController _authController = Get.find<AuthController>();
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
@ -47,14 +49,99 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
loadPermintaanPenjadwalanData();
|
loadPermintaanPenjadwalanData();
|
||||||
loadLokasiPenyaluranData();
|
loadLokasiPenyaluranData();
|
||||||
loadKategoriBantuanData();
|
loadKategoriBantuanData();
|
||||||
|
|
||||||
|
// Jalankan timer untuk memeriksa jadwal secara berkala
|
||||||
|
_startJadwalCheckTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
searchController.dispose();
|
searchController.dispose();
|
||||||
|
// Hentikan timer jika ada
|
||||||
|
_stopJadwalCheckTimer();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timer untuk memeriksa jadwal secara berkala
|
||||||
|
Timer? _jadwalCheckTimer;
|
||||||
|
|
||||||
|
void _startJadwalCheckTimer() {
|
||||||
|
// Periksa jadwal setiap 1 menit
|
||||||
|
_jadwalCheckTimer = Timer.periodic(const Duration(minutes: 1), (_) {
|
||||||
|
checkAndUpdateJadwalStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Periksa jadwal segera saat aplikasi dimulai
|
||||||
|
checkAndUpdateJadwalStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopJadwalCheckTimer() {
|
||||||
|
_jadwalCheckTimer?.cancel();
|
||||||
|
_jadwalCheckTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = [];
|
||||||
|
|
||||||
|
for (var jadwal in jadwalMendatang) {
|
||||||
|
if (jadwal.tanggalPenyaluran != null) {
|
||||||
|
// Konversi tanggal jadwal ke timezone lokal
|
||||||
|
final jadwalDateTime =
|
||||||
|
DateTimeHelper.toLocalDateTime(jadwal.tanggalPenyaluran!);
|
||||||
|
final jadwalDate = DateTime(
|
||||||
|
jadwalDateTime.year,
|
||||||
|
jadwalDateTime.month,
|
||||||
|
jadwalDateTime.day,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Jika tanggal jadwal adalah hari ini
|
||||||
|
if (isSameDay(jadwalDate, today)) {
|
||||||
|
jadwalToUpdate.add(jadwal);
|
||||||
|
|
||||||
|
// Jika waktu jadwal sudah tiba atau lewat
|
||||||
|
if (now.isAfter(jadwalDateTime) ||
|
||||||
|
now.isAtSameMomentAs(jadwalDateTime)) {
|
||||||
|
// Ubah status menjadi BERLANGSUNG (aktif)
|
||||||
|
await _supabaseService.updateJadwalStatus(
|
||||||
|
jadwal.id!, 'BERLANGSUNG');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh data setelah pembaruan
|
||||||
|
if (jadwalToUpdate.isNotEmpty) {
|
||||||
|
await loadJadwalData();
|
||||||
|
|
||||||
|
// Tampilkan notifikasi jika ada jadwal yang dipindahkan
|
||||||
|
Get.snackbar(
|
||||||
|
'Jadwal Diperbarui',
|
||||||
|
'${jadwalToUpdate.length} jadwal dipindahkan ke section Hari Ini',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error checking and updating jadwal status: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method untuk memeriksa apakah dua tanggal adalah hari yang sama
|
||||||
|
bool isSameDay(DateTime date1, DateTime date2) {
|
||||||
|
return date1.year == date2.year &&
|
||||||
|
date1.month == date2.month &&
|
||||||
|
date1.day == date2.day;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadJadwalData() async {
|
Future<void> loadJadwalData() async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -155,7 +242,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Jadwal berhasil disetujui',
|
'Jadwal berhasil disetujui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -164,7 +251,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menyetujui jadwal: ${e.toString()}',
|
'Gagal menyetujui jadwal: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -181,7 +268,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Jadwal berhasil ditolak',
|
'Jadwal berhasil ditolak',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -190,7 +277,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menolak jadwal: ${e.toString()}',
|
'Gagal menolak jadwal: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -207,7 +294,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Jadwal berhasil diselesaikan',
|
'Jadwal berhasil diselesaikan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -216,7 +303,7 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menyelesaikan jadwal: ${e.toString()}',
|
'Gagal menyelesaikan jadwal: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -78,7 +78,7 @@ class LaporanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Laporan berhasil dibuat',
|
'Laporan berhasil dibuat',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -88,7 +88,7 @@ class LaporanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal membuat laporan: ${e.toString()}',
|
'Gagal membuat laporan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -106,7 +106,7 @@ class LaporanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Laporan berhasil diunduh',
|
'Laporan berhasil diunduh',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -116,7 +116,7 @@ class LaporanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengunduh laporan: ${e.toString()}',
|
'Gagal mengunduh laporan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -133,7 +133,7 @@ class LaporanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Laporan berhasil dihapus',
|
'Laporan berhasil dihapus',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -142,7 +142,7 @@ class LaporanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menghapus laporan: ${e.toString()}',
|
'Gagal menghapus laporan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -102,7 +102,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penerima bantuan berhasil ditambahkan',
|
'Penerima bantuan berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -111,7 +111,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menambahkan penerima bantuan: ${e.toString()}',
|
'Gagal menambahkan penerima bantuan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -147,7 +147,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penerima bantuan berhasil diperbarui',
|
'Penerima bantuan berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -156,7 +156,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memperbarui penerima bantuan: ${e.toString()}',
|
'Gagal memperbarui penerima bantuan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -173,7 +173,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penerima bantuan berhasil dinonaktifkan',
|
'Penerima bantuan berhasil dinonaktifkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -182,7 +182,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menonaktifkan penerima bantuan: ${e.toString()}',
|
'Gagal menonaktifkan penerima bantuan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -199,7 +199,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penerima bantuan berhasil diaktifkan',
|
'Penerima bantuan berhasil diaktifkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -208,7 +208,7 @@ class PenerimaBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengaktifkan penerima bantuan: ${e.toString()}',
|
'Gagal mengaktifkan penerima bantuan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -76,7 +76,7 @@ class PengaduanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Pengaduan berhasil diproses',
|
'Pengaduan berhasil diproses',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -85,7 +85,7 @@ class PengaduanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memproses pengaduan: ${e.toString()}',
|
'Gagal memproses pengaduan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -120,7 +120,7 @@ class PengaduanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Tindakan berhasil ditambahkan',
|
'Tindakan berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -129,7 +129,7 @@ class PengaduanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menambahkan tindakan: ${e.toString()}',
|
'Gagal menambahkan tindakan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -146,7 +146,7 @@ class PengaduanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Pengaduan berhasil diselesaikan',
|
'Pengaduan berhasil diselesaikan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -155,7 +155,7 @@ class PengaduanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menyelesaikan pengaduan: ${e.toString()}',
|
'Gagal menyelesaikan pengaduan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -214,7 +214,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengambil gambar: ${e.toString()}',
|
'Gagal mengambil gambar: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -237,7 +237,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengambil gambar: ${e.toString()}',
|
'Gagal mengambil gambar: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -261,7 +261,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Foto bantuan harus diupload',
|
'Foto bantuan harus diupload',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -291,7 +291,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penitipan bantuan berhasil ditambahkan',
|
'Penitipan bantuan berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -300,7 +300,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menambahkan penitipan: ${e.toString()}',
|
'Gagal menambahkan penitipan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -315,7 +315,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Bukti serah terima harus diupload',
|
'Bukti serah terima harus diupload',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -339,7 +339,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penitipan berhasil diverifikasi',
|
'Penitipan berhasil diverifikasi',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -348,7 +348,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memverifikasi penitipan: ${e.toString()}',
|
'Gagal memverifikasi penitipan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -369,7 +369,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Penitipan berhasil ditolak',
|
'Penitipan berhasil ditolak',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -378,7 +378,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menolak penitipan: ${e.toString()}',
|
'Gagal menolak penitipan: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -666,7 +666,7 @@ class PenitipanBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menambahkan donatur: ${e.toString()}',
|
'Gagal menambahkan donatur: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -19,6 +19,10 @@ class PetugasDesaController extends GetxController {
|
|||||||
// Controller untuk pencarian
|
// Controller untuk pencarian
|
||||||
final TextEditingController searchController = TextEditingController();
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
// Controller untuk pencarian penerima
|
||||||
|
final TextEditingController searchPenerimaController =
|
||||||
|
TextEditingController();
|
||||||
|
|
||||||
// Data profil pengguna dari cache
|
// Data profil pengguna dari cache
|
||||||
final RxMap<String, dynamic> userProfile = RxMap<String, dynamic>({});
|
final RxMap<String, dynamic> userProfile = RxMap<String, dynamic>({});
|
||||||
|
|
||||||
@ -28,6 +32,23 @@ class PetugasDesaController extends GetxController {
|
|||||||
// Data jadwal hari ini
|
// Data jadwal hari ini
|
||||||
final RxList<dynamic> jadwalHariIni = <dynamic>[].obs;
|
final RxList<dynamic> jadwalHariIni = <dynamic>[].obs;
|
||||||
|
|
||||||
|
// Data penerima penyaluran
|
||||||
|
final RxList<Map<String, dynamic>> penerimaPenyaluran =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
final RxList<Map<String, dynamic>> filteredPenerima =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
final RxInt jumlahPenerima = 0.obs;
|
||||||
|
final RxString filterStatus = 'SEMUA'.obs;
|
||||||
|
|
||||||
|
// Tambahkan variabel isLoading
|
||||||
|
final isLoading = false.obs;
|
||||||
|
|
||||||
|
// Tambahkan instance supabaseService yang sudah diinisialisasi
|
||||||
|
final supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
// Variabel untuk pencarian dan filter
|
||||||
|
final searchQuery = ''.obs;
|
||||||
|
|
||||||
UserModel? get user => _authController.user;
|
UserModel? get user => _authController.user;
|
||||||
String get role => user?.role ?? 'PETUGASDESA';
|
String get role => user?.role ?? 'PETUGASDESA';
|
||||||
String get nama => user?.name ?? 'Petugas Desa';
|
String get nama => user?.name ?? 'Petugas Desa';
|
||||||
@ -81,6 +102,7 @@ class PetugasDesaController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
searchController.dispose();
|
searchController.dispose();
|
||||||
|
searchPenerimaController.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +238,258 @@ class PetugasDesaController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memastikan format UUID yang benar
|
||||||
|
String ensureValidUUID(String id) {
|
||||||
|
// Jika ID sudah dalam format UUID yang benar, kembalikan apa adanya
|
||||||
|
if (id.contains('-') && id.length == 36) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika ID adalah string UUID tanpa tanda hubung, tambahkan tanda hubung
|
||||||
|
if (id.length == 32) {
|
||||||
|
return '${id.substring(0, 8)}-${id.substring(8, 12)}-${id.substring(12, 16)}-${id.substring(16, 20)}-${id.substring(20)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika format tidak dikenali, kembalikan apa adanya
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk memuat ulang data penerima
|
||||||
|
Future<void> reloadPenerimaPenyaluran() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// Gunakan data dummy sementara
|
||||||
|
final dummyData = _createDummyPenerimaPenyaluran();
|
||||||
|
penerimaPenyaluran.value = dummyData;
|
||||||
|
jumlahPenerima.value = dummyData.length;
|
||||||
|
print(
|
||||||
|
'Data dummy penerima berhasil dimuat: ${penerimaPenyaluran.length} data');
|
||||||
|
} catch (e) {
|
||||||
|
print('Error saat memuat data dummy penerima: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Membuat data dummy penerima penyaluran
|
||||||
|
List<Map<String, dynamic>> _createDummyPenerimaPenyaluran() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'penyaluran_bantuan_id': 'a2dabc5a-761f-4f11-9fbe-a376768880c3',
|
||||||
|
'warga_id': 'warga-001',
|
||||||
|
'status_penerimaan': 'SUDAHMENERIMA',
|
||||||
|
'jumlah_bantuan': 1,
|
||||||
|
'created_at': '2023-01-01',
|
||||||
|
'warga': {
|
||||||
|
'id': 'warga-001',
|
||||||
|
'nama_lengkap': 'Budi Santoso',
|
||||||
|
'nik': '3201234567890001',
|
||||||
|
'alamat': 'Jl. Merdeka No. 123, RT 01/RW 02',
|
||||||
|
'jenis_kelamin': 'L',
|
||||||
|
'tanggal_lahir': '1980-01-01',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 2,
|
||||||
|
'penyaluran_bantuan_id': 'a2dabc5a-761f-4f11-9fbe-a376768880c3',
|
||||||
|
'warga_id': 'warga-002',
|
||||||
|
'status_penerimaan': 'BELUMMENERIMA',
|
||||||
|
'jumlah_bantuan': 1,
|
||||||
|
'created_at': '2023-01-01',
|
||||||
|
'warga': {
|
||||||
|
'id': 'warga-002',
|
||||||
|
'nama_lengkap': 'Siti Aminah',
|
||||||
|
'nik': '3201234567890002',
|
||||||
|
'alamat': 'Jl. Pahlawan No. 45, RT 03/RW 04',
|
||||||
|
'jenis_kelamin': 'P',
|
||||||
|
'tanggal_lahir': '1985-05-15',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 3,
|
||||||
|
'penyaluran_bantuan_id': 'a2dabc5a-761f-4f11-9fbe-a376768880c3',
|
||||||
|
'warga_id': 'warga-003',
|
||||||
|
'status_penerimaan': 'SUDAHMENERIMA',
|
||||||
|
'jumlah_bantuan': 1,
|
||||||
|
'created_at': '2023-01-01',
|
||||||
|
'warga': {
|
||||||
|
'id': 'warga-003',
|
||||||
|
'nama_lengkap': 'Ahmad Hidayat',
|
||||||
|
'nik': '3201234567890003',
|
||||||
|
'alamat': 'Jl. Cendrawasih No. 78, RT 05/RW 06',
|
||||||
|
'jenis_kelamin': 'L',
|
||||||
|
'tanggal_lahir': '1975-12-10',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 4,
|
||||||
|
'penyaluran_bantuan_id': 'a2dabc5a-761f-4f11-9fbe-a376768880c3',
|
||||||
|
'warga_id': 'warga-004',
|
||||||
|
'status_penerimaan': 'BELUMMENERIMA',
|
||||||
|
'jumlah_bantuan': 1,
|
||||||
|
'created_at': '2023-01-01',
|
||||||
|
'warga': {
|
||||||
|
'id': 'warga-004',
|
||||||
|
'nama_lengkap': 'Dewi Lestari',
|
||||||
|
'nik': '3201234567890004',
|
||||||
|
'alamat': 'Jl. Mawar No. 12, RT 07/RW 08',
|
||||||
|
'jenis_kelamin': 'P',
|
||||||
|
'tanggal_lahir': '1990-08-22',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 5,
|
||||||
|
'penyaluran_bantuan_id': 'a2dabc5a-761f-4f11-9fbe-a376768880c3',
|
||||||
|
'warga_id': 'warga-005',
|
||||||
|
'status_penerimaan': 'SUDAHMENERIMA',
|
||||||
|
'jumlah_bantuan': 1,
|
||||||
|
'created_at': '2023-01-01',
|
||||||
|
'warga': {
|
||||||
|
'id': 'warga-005',
|
||||||
|
'nama_lengkap': 'Joko Widodo',
|
||||||
|
'nik': '3201234567890005',
|
||||||
|
'alamat': 'Jl. Kenanga No. 56, RT 09/RW 10',
|
||||||
|
'jenis_kelamin': 'L',
|
||||||
|
'tanggal_lahir': '1965-06-30',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk menginisialisasi data penerima penyaluran
|
||||||
|
void initPenerimaPenyaluran(List<Map<String, dynamic>> data) {
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Inisialisasi penerima penyaluran dengan ${data.length} item');
|
||||||
|
|
||||||
|
// Periksa struktur data
|
||||||
|
if (data.isNotEmpty) {
|
||||||
|
final firstItem = data.first;
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Struktur data penerima: ${firstItem.keys.join(', ')}');
|
||||||
|
|
||||||
|
if (firstItem.containsKey('warga')) {
|
||||||
|
final warga = firstItem['warga'];
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Struktur data warga: ${warga != null ? (warga is Map ? warga.keys.join(', ') : 'bukan Map') : 'null'}');
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Data warga tidak ditemukan dalam item penerima');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
penerimaPenyaluran.value = data;
|
||||||
|
filteredPenerima.value = data;
|
||||||
|
jumlahPenerima.value = data.length;
|
||||||
|
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Selesai inisialisasi, jumlah penerima: ${jumlahPenerima.value}');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk memfilter penerima berdasarkan kata kunci
|
||||||
|
void filterPenerima(String keyword) {
|
||||||
|
print('DEBUG CONTROLLER: Memfilter penerima dengan keyword: "$keyword"');
|
||||||
|
|
||||||
|
if (keyword.isEmpty) {
|
||||||
|
print('DEBUG CONTROLLER: Keyword kosong, menerapkan filter status saja');
|
||||||
|
applyFilters();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final lowercaseKeyword = keyword.toLowerCase();
|
||||||
|
final filtered = penerimaPenyaluran.where((penerima) {
|
||||||
|
final warga = penerima['warga'] as Map<String, dynamic>?;
|
||||||
|
if (warga == null) {
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Data warga null untuk penerima: ${penerima['id']}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final namaLengkap =
|
||||||
|
(warga['nama_lengkap'] ?? '').toString().toLowerCase();
|
||||||
|
final nik = (warga['nik'] ?? '').toString().toLowerCase();
|
||||||
|
final alamat = (warga['alamat'] ?? '').toString().toLowerCase();
|
||||||
|
|
||||||
|
final matches = namaLengkap.contains(lowercaseKeyword) ||
|
||||||
|
nik.contains(lowercaseKeyword) ||
|
||||||
|
alamat.contains(lowercaseKeyword);
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Hasil filter: ${filtered.length} dari ${penerimaPenyaluran.length} item');
|
||||||
|
filteredPenerima.value = filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk menerapkan filter status
|
||||||
|
void applyFilters() {
|
||||||
|
final keyword = searchPenerimaController.text.toLowerCase();
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Menerapkan filter dengan status: ${filterStatus.value}, keyword: "$keyword"');
|
||||||
|
|
||||||
|
if (filterStatus.value == 'SEMUA' && keyword.isEmpty) {
|
||||||
|
print('DEBUG CONTROLLER: Tidak ada filter, menampilkan semua data');
|
||||||
|
filteredPenerima.value = penerimaPenyaluran;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final filtered = penerimaPenyaluran.where((penerima) {
|
||||||
|
bool statusMatch = true;
|
||||||
|
if (filterStatus.value != 'SEMUA') {
|
||||||
|
statusMatch = penerima['status_penerimaan'] == filterStatus.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyword.isEmpty) return statusMatch;
|
||||||
|
|
||||||
|
final warga = penerima['warga'] as Map<String, dynamic>?;
|
||||||
|
if (warga == null) return false;
|
||||||
|
|
||||||
|
final namaLengkap =
|
||||||
|
(warga['nama_lengkap'] ?? '').toString().toLowerCase();
|
||||||
|
final nik = (warga['nik'] ?? '').toString().toLowerCase();
|
||||||
|
final alamat = (warga['alamat'] ?? '').toString().toLowerCase();
|
||||||
|
|
||||||
|
final keywordMatch = namaLengkap.contains(keyword) ||
|
||||||
|
nik.contains(keyword) ||
|
||||||
|
alamat.contains(keyword);
|
||||||
|
|
||||||
|
return statusMatch && keywordMatch;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Hasil filter gabungan: ${filtered.length} dari ${penerimaPenyaluran.length} item');
|
||||||
|
filteredPenerima.value = filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk memperbarui status penerimaan bantuan
|
||||||
|
Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
||||||
|
{DateTime? tanggalPenerimaan,
|
||||||
|
String? buktiPenerimaan,
|
||||||
|
String? keterangan}) async {
|
||||||
|
try {
|
||||||
|
final result = await _supabaseService.updateStatusPenerimaan(
|
||||||
|
penerimaId, status,
|
||||||
|
tanggalPenerimaan: tanggalPenerimaan,
|
||||||
|
buktiPenerimaan: buktiPenerimaan,
|
||||||
|
keterangan: keterangan);
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating status penerimaan: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk menyelesaikan jadwal penyaluran
|
||||||
|
Future<void> completeJadwal(String jadwalId) async {
|
||||||
|
try {
|
||||||
|
await _supabaseService.completeJadwal(jadwalId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error completing jadwal: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Metode untuk mengubah tab aktif
|
// Metode untuk mengubah tab aktif
|
||||||
void changeTab(int index) {
|
void changeTab(int index) {
|
||||||
activeTabIndex.value = index;
|
activeTabIndex.value = index;
|
||||||
@ -251,4 +525,76 @@ class PetugasDesaController extends GetxController {
|
|||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
await _authController.logout();
|
await _authController.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk debugging struktur data jadwal
|
||||||
|
void debugJadwalData(Map<String, dynamic> jadwal) {
|
||||||
|
print('DEBUG CONTROLLER: ===== DEBUGGING JADWAL DATA =====');
|
||||||
|
print('DEBUG CONTROLLER: Keys dalam jadwal: ${jadwal.keys.join(', ')}');
|
||||||
|
|
||||||
|
// Periksa ID
|
||||||
|
final id = jadwal['id'];
|
||||||
|
print('DEBUG CONTROLLER: ID jadwal: $id (${id.runtimeType})');
|
||||||
|
|
||||||
|
// Periksa data lain yang penting
|
||||||
|
print('DEBUG CONTROLLER: Nama: ${jadwal['nama']}');
|
||||||
|
print('DEBUG CONTROLLER: Status: ${jadwal['status']}');
|
||||||
|
print('DEBUG CONTROLLER: Jumlah penerima: ${jadwal['jumlah_penerima']}');
|
||||||
|
|
||||||
|
// Periksa apakah ada data yang null
|
||||||
|
jadwal.forEach((key, value) {
|
||||||
|
if (value == null) {
|
||||||
|
print('DEBUG CONTROLLER: Field "$key" bernilai null');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
print('DEBUG CONTROLLER: ===== END DEBUGGING JADWAL DATA =====');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan daftar penerima penyaluran
|
||||||
|
Future<List<Map<String, dynamic>>?> getPenerimaPenyaluran(
|
||||||
|
String penyaluranId) async {
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Mengambil data penerima untuk penyaluran ID: $penyaluranId');
|
||||||
|
// Gunakan data dummy sementara
|
||||||
|
final dummyData = _createDummyPenerimaPenyaluran();
|
||||||
|
print(
|
||||||
|
'DEBUG CONTROLLER: Mengembalikan ${dummyData.length} data dummy penerima');
|
||||||
|
return dummyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk memfilter data penerima berdasarkan status dan pencarian
|
||||||
|
List<Map<String, dynamic>> get filteredPenerimaPenyaluran {
|
||||||
|
if (penerimaPenyaluran.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> filtered =
|
||||||
|
List<Map<String, dynamic>>.from(penerimaPenyaluran);
|
||||||
|
|
||||||
|
// Filter berdasarkan status
|
||||||
|
if (filterStatus.value != 'SEMUA') {
|
||||||
|
filtered = filtered.where((penerima) {
|
||||||
|
return penerima['status_penerimaan'] == filterStatus.value;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter berdasarkan pencarian
|
||||||
|
if (searchQuery.value.isNotEmpty) {
|
||||||
|
final query = searchQuery.value.toLowerCase();
|
||||||
|
filtered = filtered.where((penerima) {
|
||||||
|
final warga = penerima['warga'] as Map<String, dynamic>?;
|
||||||
|
if (warga == null) return false;
|
||||||
|
|
||||||
|
final nama = (warga['nama_lengkap'] ?? '').toString().toLowerCase();
|
||||||
|
final nik = (warga['nik'] ?? '').toString().toLowerCase();
|
||||||
|
final alamat = (warga['alamat'] ?? '').toString().toLowerCase();
|
||||||
|
|
||||||
|
return nama.contains(query) ||
|
||||||
|
nik.contains(query) ||
|
||||||
|
alamat.contains(query);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ class StokBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Stok bantuan berhasil ditambahkan',
|
'Stok bantuan berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -132,7 +132,7 @@ class StokBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menambahkan stok bantuan: $e',
|
'Gagal menambahkan stok bantuan: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -155,7 +155,7 @@ class StokBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Stok bantuan berhasil diperbarui',
|
'Stok bantuan berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -164,7 +164,7 @@ class StokBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memperbarui stok bantuan: $e',
|
'Gagal memperbarui stok bantuan: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -178,7 +178,7 @@ class StokBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Stok bantuan berhasil dihapus',
|
'Stok bantuan berhasil dihapus',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -187,7 +187,7 @@ class StokBantuanController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menghapus stok bantuan: $e',
|
'Gagal menghapus stok bantuan: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -112,7 +112,7 @@ class NotifikasiView extends GetView<PetugasDesaController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Notifikasi',
|
'Notifikasi',
|
||||||
'Semua notifikasi telah ditandai sebagai dibaca',
|
'Semua notifikasi telah ditandai sebagai dibaca',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: AppTheme.primaryColor,
|
backgroundColor: AppTheme.primaryColor,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -171,7 +171,7 @@ class NotifikasiView extends GetView<PetugasDesaController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Notifikasi',
|
'Notifikasi',
|
||||||
'Notifikasi ditandai sebagai dibaca',
|
'Notifikasi ditandai sebagai dibaca',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: AppTheme.primaryColor,
|
backgroundColor: AppTheme.primaryColor,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -4,24 +4,82 @@ import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa
|
|||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
||||||
const PelaksanaanPenyaluranView({super.key});
|
const PelaksanaanPenyaluranView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Ambil data jadwal dari parameter
|
// Ambil data jadwal dari parameter
|
||||||
final jadwal = Get.arguments as Map<String, dynamic>;
|
final jadwal = Get.arguments as Map<String, dynamic>;
|
||||||
|
|
||||||
|
// Debug: Tampilkan data jadwal yang diterima
|
||||||
|
print('DEBUG: Jadwal yang diterima: $jadwal');
|
||||||
|
print('DEBUG: ID Jadwal: ${jadwal['id']}');
|
||||||
|
|
||||||
|
// Debug: Periksa koneksi ke Supabase menggunakan instance dari controller
|
||||||
|
try {
|
||||||
|
controller.supabaseService.client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('id')
|
||||||
|
.limit(1)
|
||||||
|
.then((_) {
|
||||||
|
print('DEBUG: Koneksi ke Supabase berhasil');
|
||||||
|
}).catchError((error) {
|
||||||
|
print('DEBUG: Koneksi ke Supabase gagal: $error');
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('DEBUG: Error saat memeriksa koneksi Supabase: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Periksa struktur data jadwal
|
||||||
|
controller.debugJadwalData(jadwal);
|
||||||
|
|
||||||
|
// Muat data penerima saat halaman dimuat
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
controller.reloadPenerimaPenyaluran();
|
||||||
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Detail Pelaksanaan Penyaluran'),
|
title: const Text('Pelaksanaan Penyaluran'),
|
||||||
elevation: 0,
|
// actions: [
|
||||||
|
// // Tombol debug untuk melihat SQL query
|
||||||
|
// IconButton(
|
||||||
|
// icon: const Icon(Icons.code),
|
||||||
|
// onPressed: () {
|
||||||
|
// final penyaluranId = Get.parameters['id'] ?? jadwal['id'];
|
||||||
|
// _showSqlDebugDialog(context, penyaluranId);
|
||||||
|
// },
|
||||||
|
// tooltip: 'Lihat SQL Query',
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Header dengan informasi pelaksanaan
|
// Informasi jadwal
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withAlpha(26),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
_buildHeaderInfo(context, jadwal),
|
_buildHeaderInfo(context, jadwal),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Daftar penerima bantuan
|
// Daftar penerima bantuan
|
||||||
_buildDaftarPenerima(context, jadwal),
|
_buildDaftarPenerima(context, jadwal),
|
||||||
@ -35,52 +93,41 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
Widget _buildHeaderInfo(BuildContext context, Map<String, dynamic> jadwal) {
|
Widget _buildHeaderInfo(BuildContext context, Map<String, dynamic> jadwal) {
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return Container(
|
return Column(
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: AppTheme.primaryGradient,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
jadwal['lokasi'] ?? 'Lokasi Penyaluran',
|
jadwal['lokasi'] ?? 'Lokasi Penyaluran',
|
||||||
style: textTheme.headlineSmall?.copyWith(
|
style: textTheme.titleLarge?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildInfoItem(context,
|
|
||||||
icon: Icons.category,
|
|
||||||
label: 'Kategori Bantuan',
|
|
||||||
value: jadwal['kategori_bantuan'] ?? '-'),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoItem(context,
|
|
||||||
icon: Icons.calendar_today,
|
|
||||||
label: 'Tanggal',
|
|
||||||
value: jadwal['tanggal'] ?? '-'),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoItem(context,
|
|
||||||
icon: Icons.access_time,
|
|
||||||
label: 'Waktu',
|
|
||||||
value: jadwal['waktu'] ?? '-'),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoItem(context,
|
|
||||||
icon: Icons.people,
|
|
||||||
label: 'Jumlah Penerima',
|
|
||||||
value: '${jadwal['jumlah_penerima'] ?? 0} orang'),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildInfoItem(
|
_buildInfoItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.flag,
|
icon: Icons.category,
|
||||||
label: 'Status',
|
label: 'Jenis Bantuan',
|
||||||
value: jadwal['status'] ?? 'Aktif',
|
value: jadwal['jenis_bantuan'] ?? 'Tidak tersedia',
|
||||||
isStatus: true,
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.calendar_today,
|
||||||
|
label: 'Tanggal',
|
||||||
|
value: jadwal['tanggal'] ?? 'Tidak tersedia',
|
||||||
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.access_time,
|
||||||
|
label: 'Waktu',
|
||||||
|
value: jadwal['waktu'] ?? 'Tidak tersedia',
|
||||||
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.people,
|
||||||
|
label: 'Jumlah Penerima',
|
||||||
|
value: '${controller.jumlahPenerima} orang',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,88 +138,79 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
required String value,
|
required String value,
|
||||||
bool isStatus = false,
|
bool isStatus = false,
|
||||||
}) {
|
}) {
|
||||||
Color statusColor = Colors.white;
|
final bool isActive = isStatus && value.toUpperCase() == 'AKTIF';
|
||||||
if (isStatus) {
|
|
||||||
switch (value.toLowerCase()) {
|
|
||||||
case 'aktif':
|
|
||||||
statusColor = Colors.green;
|
|
||||||
break;
|
|
||||||
case 'terjadwal':
|
|
||||||
statusColor = Colors.blue;
|
|
||||||
break;
|
|
||||||
case 'selesai':
|
|
||||||
statusColor = Colors.grey;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
statusColor = Colors.orange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Row(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
color: Colors.white,
|
size: 18,
|
||||||
size: 20,
|
color: Colors.grey[600],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'$label: ',
|
'$label: ',
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
if (isStatus)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isActive ? Colors.green[50] : Colors.orange[50],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
value,
|
value,
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: TextStyle(
|
||||||
color: isStatus ? statusColor : Colors.white,
|
color: isActive ? Colors.green : Colors.orange,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDaftarPenerima(
|
Widget _buildDaftarPenerima(
|
||||||
BuildContext context, Map<String, dynamic> jadwal) {
|
BuildContext context, Map<String, dynamic> jadwal) {
|
||||||
// Simulasi data penerima bantuan
|
// Debug: Periksa validitas ID penyaluran
|
||||||
final List<Map<String, dynamic>> daftarPenerima = [
|
final penyaluranId = jadwal['id'];
|
||||||
{
|
if (penyaluranId == null || penyaluranId.toString().isEmpty) {
|
||||||
'id': '1',
|
print('DEBUG: PERINGATAN! ID penyaluran kosong atau null: $penyaluranId');
|
||||||
'nama': 'Ahmad Sulaiman',
|
|
||||||
'nik': '3201234567890001',
|
// Tampilkan pesan error jika ID tidak valid
|
||||||
'alamat': 'Dusun Sukamaju RT 02/03',
|
return Padding(
|
||||||
'status': 'belum_diterima',
|
padding: const EdgeInsets.all(16),
|
||||||
},
|
child: Center(
|
||||||
{
|
child: Column(
|
||||||
'id': '2',
|
children: [
|
||||||
'nama': 'Siti Aminah',
|
const Icon(Icons.error_outline, color: Colors.red, size: 48),
|
||||||
'nik': '3201234567890002',
|
const SizedBox(height: 16),
|
||||||
'alamat': 'Dusun Sukamaju RT 01/03',
|
Text(
|
||||||
'status': 'sudah_diterima',
|
'ID penyaluran tidak valid: $penyaluranId',
|
||||||
},
|
style: TextStyle(color: Colors.red),
|
||||||
{
|
textAlign: TextAlign.center,
|
||||||
'id': '3',
|
),
|
||||||
'nama': 'Budi Santoso',
|
],
|
||||||
'nik': '3201234567890003',
|
),
|
||||||
'alamat': 'Dusun Sukamaju RT 03/01',
|
),
|
||||||
'status': 'belum_diterima',
|
);
|
||||||
},
|
}
|
||||||
{
|
|
||||||
'id': '4',
|
|
||||||
'nama': 'Dewi Lestari',
|
|
||||||
'nik': '3201234567890004',
|
|
||||||
'alamat': 'Dusun Sukamaju RT 04/02',
|
|
||||||
'status': 'sudah_diterima',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '5',
|
|
||||||
'nama': 'Joko Widodo',
|
|
||||||
'nik': '3201234567890005',
|
|
||||||
'alamat': 'Dusun Sukamaju RT 05/01',
|
|
||||||
'status': 'belum_diterima',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@ -188,18 +226,20 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Obx(() => Text(
|
||||||
'${daftarPenerima.length} orang',
|
'${controller.jumlahPenerima.value} orang',
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: Colors.grey.shade600,
|
color: Colors.grey.shade600,
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Search bar
|
// Search bar
|
||||||
TextField(
|
TextField(
|
||||||
|
controller: controller.searchPenerimaController,
|
||||||
|
onChanged: (value) => controller.filterPenerima(value),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Cari penerima...',
|
hintText: 'Cari penerima...',
|
||||||
prefixIcon: const Icon(Icons.search),
|
prefixIcon: const Icon(Icons.search),
|
||||||
@ -224,31 +264,96 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_buildFilterChip(context, 'Semua', true),
|
_buildFilterChip(
|
||||||
|
context, 'Semua', controller.filterStatus.value == 'SEMUA'),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildFilterChip(context, 'Sudah Diterima', false),
|
_buildFilterChip(context, 'Sudah Diterima',
|
||||||
|
controller.filterStatus.value == 'SUDAHMENERIMA'),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildFilterChip(context, 'Belum Diterima', false),
|
_buildFilterChip(context, 'Belum Diterima',
|
||||||
|
controller.filterStatus.value == 'BELUMMENERIMA'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Daftar penerima
|
// Daftar penerima - gunakan SizedBox dengan height tertentu daripada Expanded
|
||||||
...daftarPenerima
|
SizedBox(
|
||||||
.map((penerima) => _buildPenerimaItem(context, penerima)),
|
height: 400, // Tinggi tetap, sesuaikan sesuai kebutuhan
|
||||||
|
child: Obx(() {
|
||||||
|
// Tampilkan loading jika sedang memuat ulang data
|
||||||
|
if (controller.isLoading.value) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tampilkan pesan jika tidak ada data
|
||||||
|
if (controller.filteredPenerimaPenyaluran.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.people_outline,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 60,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Tidak ada data penerima untuk penyaluran ini',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
controller.reloadPenerimaPenyaluran();
|
||||||
|
},
|
||||||
|
child: const Text('Refresh Data'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tampilkan data penerima
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: controller.filteredPenerimaPenyaluran.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final penerima = controller.filteredPenerimaPenyaluran[index];
|
||||||
|
return _buildPenerimaItem(context, penerima);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFilterChip(BuildContext context, String label, bool isSelected) {
|
Widget _buildFilterChip(BuildContext context, String label, bool isSelected) {
|
||||||
|
String statusValue;
|
||||||
|
switch (label) {
|
||||||
|
case 'Sudah Diterima':
|
||||||
|
statusValue = 'SUDAHMENERIMA';
|
||||||
|
break;
|
||||||
|
case 'Belum Diterima':
|
||||||
|
statusValue = 'BELUMMENERIMA';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusValue = 'SEMUA';
|
||||||
|
}
|
||||||
|
|
||||||
return FilterChip(
|
return FilterChip(
|
||||||
label: Text(label),
|
label: Text(label),
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
onSelected: (selected) {
|
onSelected: (selected) {
|
||||||
// Implementasi filter
|
if (selected) {
|
||||||
|
controller.filterStatus.value = statusValue;
|
||||||
|
controller.applyFilters();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.grey.shade100,
|
backgroundColor: Colors.grey.shade100,
|
||||||
selectedColor: AppTheme.primaryColor.withOpacity(0.2),
|
selectedColor: AppTheme.primaryColor.withOpacity(0.2),
|
||||||
@ -260,9 +365,55 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk menampilkan dialog debug
|
||||||
|
void _showDebugDialog(BuildContext context, Map<String, dynamic> data) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Debug Data'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text('Data Struktur:',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text('Keys: ${data.keys.toList().join(', ')}'),
|
||||||
|
const Divider(),
|
||||||
|
if (data.containsKey('warga')) ...[
|
||||||
|
const Text('Warga Data:',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (data['warga'] != null)
|
||||||
|
Text(
|
||||||
|
'Warga Keys: ${(data['warga'] as Map<String, dynamic>).keys.toList().join(', ')}')
|
||||||
|
else
|
||||||
|
const Text('Warga data is null'),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
const Text('Raw Data:',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(data.toString(), style: const TextStyle(fontSize: 12)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('Tutup'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk membangun item penerima dengan tombol debug
|
||||||
Widget _buildPenerimaItem(
|
Widget _buildPenerimaItem(
|
||||||
BuildContext context, Map<String, dynamic> penerima) {
|
BuildContext context, Map<String, dynamic> penerima) {
|
||||||
final bool sudahDiterima = penerima['status'] == 'sudah_diterima';
|
final bool sudahDiterima = penerima['status_penerimaan'] == 'SUDAHMENERIMA';
|
||||||
|
final warga = penerima['warga'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
@ -281,7 +432,7 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
title: Text(
|
title: Text(
|
||||||
penerima['nama'] ?? '',
|
warga?['nama_lengkap'] ?? 'Nama tidak tersedia',
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@ -290,12 +441,26 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text('NIK: ${penerima['nik'] ?? ''}'),
|
Text('NIK: ${warga?['nik'] ?? 'NIK tidak tersedia'}'),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text('Alamat: ${penerima['alamat'] ?? ''}'),
|
Text('Alamat: ${warga?['alamat'] ?? 'Alamat tidak tersedia'}'),
|
||||||
|
if (penerima['jumlah_bantuan'] != null) ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text('Jumlah Bantuan: ${penerima['jumlah_bantuan']}'),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: Container(
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Tombol debug untuk melihat struktur data
|
||||||
|
// IconButton(
|
||||||
|
// icon: const Icon(Icons.bug_report, color: Colors.grey),
|
||||||
|
// onPressed: () => _showDebugDialog(context, penerima),
|
||||||
|
// tooltip: 'Lihat struktur data',
|
||||||
|
// iconSize: 20,
|
||||||
|
// ),
|
||||||
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: sudahDiterima
|
color: sudahDiterima
|
||||||
@ -311,11 +476,19 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Navigasi ke halaman konfirmasi penerima
|
// Navigasi ke halaman konfirmasi penerima
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/daftar-penerima/konfirmasi',
|
'/konfirmasi-penerima',
|
||||||
arguments: penerima['id'],
|
arguments: {
|
||||||
|
'penerima_id': penerima['id'],
|
||||||
|
'penyaluran_id': penerima['penyaluran_bantuan_id'],
|
||||||
|
'warga': warga,
|
||||||
|
'status_penerimaan': penerima['status_penerimaan'],
|
||||||
|
'jumlah_bantuan': penerima['jumlah_bantuan'],
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -324,7 +497,7 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
|
|
||||||
Widget _buildBottomButtons(
|
Widget _buildBottomButtons(
|
||||||
BuildContext context, Map<String, dynamic> jadwal) {
|
BuildContext context, Map<String, dynamic> jadwal) {
|
||||||
final bool isSelesai = (jadwal['status'] ?? '').toLowerCase() == 'selesai';
|
final String status = (jadwal['status'] ?? '').toUpperCase();
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@ -341,16 +514,16 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
// Tampilkan tombol berdasarkan status
|
||||||
|
if (status == 'AKTIF') ...[
|
||||||
|
// Tombol Cetak Laporan
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: isSelesai
|
onPressed: () {
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
// Implementasi cetak laporan
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Informasi',
|
'Informasi',
|
||||||
'Mencetak laporan penyaluran...',
|
'Mencetak laporan penyaluran...',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.print),
|
icon: const Icon(Icons.print),
|
||||||
@ -366,18 +539,16 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
// Tombol Selesaikan
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: isSelesai
|
onPressed: () {
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
// Implementasi selesaikan penyaluran
|
|
||||||
_showSelesaikanDialog(context, jadwal);
|
_showSelesaikanDialog(context, jadwal);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.check_circle),
|
icon: const Icon(Icons.check_circle),
|
||||||
label: const Text('Selesaikan'),
|
label: const Text('Selesaikan'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: isSelesai ? Colors.grey : Colors.green,
|
backgroundColor: Colors.green,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@ -386,6 +557,68 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
] else if (status == 'SELESAI') ...[
|
||||||
|
// Hanya tampilkan tombol Cetak Laporan
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
Get.snackbar(
|
||||||
|
'Informasi',
|
||||||
|
'Mencetak laporan penyaluran...',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.print),
|
||||||
|
label: const Text('Cetak Laporan'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else if (status == 'DIBATALKAN') ...[
|
||||||
|
// Tampilkan pesan dibatalkan
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red[50],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Penyaluran Dibatalkan',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
// Status lainnya - tampilkan pesan default
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Status: $status',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -408,15 +641,26 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Implementasi selesaikan penyaluran
|
// Implementasi selesaikan penyaluran
|
||||||
|
controller.completeJadwal(jadwal['id']).then((_) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Get.back(); // Kembali ke halaman sebelumnya
|
Get.back(); // Kembali ke halaman sebelumnya
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Penyaluran telah diselesaikan',
|
'Penyaluran telah diselesaikan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
}).catchError((error) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Get.snackbar(
|
||||||
|
'Gagal',
|
||||||
|
'Terjadi kesalahan: $error',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
@ -427,4 +671,208 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk menampilkan filter dan pencarian
|
||||||
|
Widget _buildFilterAndSearch(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withAlpha(26),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Filter & Pencarian',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Filter status
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text('Status: '),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() {
|
||||||
|
final currentFilter = controller.filterStatus.value;
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Filter Semua
|
||||||
|
InkWell(
|
||||||
|
onTap: () => controller.filterStatus.value = 'SEMUA',
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: currentFilter == 'SEMUA'
|
||||||
|
? Colors.blue
|
||||||
|
: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Semua',
|
||||||
|
style: TextStyle(
|
||||||
|
color: currentFilter == 'SEMUA'
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black87,
|
||||||
|
fontWeight: currentFilter == 'SEMUA'
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
// Filter Sudah Menerima
|
||||||
|
InkWell(
|
||||||
|
onTap: () =>
|
||||||
|
controller.filterStatus.value = 'SUDAHMENERIMA',
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: currentFilter == 'SUDAHMENERIMA'
|
||||||
|
? Colors.blue
|
||||||
|
: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Sudah Menerima',
|
||||||
|
style: TextStyle(
|
||||||
|
color: currentFilter == 'SUDAHMENERIMA'
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black87,
|
||||||
|
fontWeight: currentFilter == 'SUDAHMENERIMA'
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
// Filter Belum Menerima
|
||||||
|
InkWell(
|
||||||
|
onTap: () =>
|
||||||
|
controller.filterStatus.value = 'BELUMMENERIMA',
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: currentFilter == 'BELUMMENERIMA'
|
||||||
|
? Colors.blue
|
||||||
|
: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Belum Menerima',
|
||||||
|
style: TextStyle(
|
||||||
|
color: currentFilter == 'BELUMMENERIMA'
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black87,
|
||||||
|
fontWeight: currentFilter == 'BELUMMENERIMA'
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Pencarian
|
||||||
|
TextField(
|
||||||
|
onChanged: (value) => controller.searchQuery.value = value,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Cari berdasarkan nama atau NIK',
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk menampilkan dialog debug SQL
|
||||||
|
void _showSqlDebugDialog(BuildContext context, String penyaluranId) {
|
||||||
|
final validId = controller.ensureValidUUID(penyaluranId);
|
||||||
|
final sqlQuery = '''
|
||||||
|
SELECT
|
||||||
|
penerima_penyaluran.*,
|
||||||
|
warga.*
|
||||||
|
FROM
|
||||||
|
penerima_penyaluran
|
||||||
|
LEFT JOIN
|
||||||
|
warga ON warga.id = penerima_penyaluran.warga_id
|
||||||
|
WHERE
|
||||||
|
penerima_penyaluran.penyaluran_bantuan_id = '$validId';
|
||||||
|
''';
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('SQL Query Debug'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text('SQL Query yang digunakan:'),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: SelectableText(
|
||||||
|
sqlQuery,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text('Petunjuk:'),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text('1. Salin query ini ke SQL Editor di Supabase'),
|
||||||
|
const Text('2. Jalankan query untuk melihat hasil'),
|
||||||
|
const Text(
|
||||||
|
'3. Bandingkan dengan data yang ditampilkan di aplikasi'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('Tutup'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -587,7 +587,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Status pengaduan berhasil diubah menjadi Tindakan',
|
'Status pengaduan berhasil diubah menjadi Tindakan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.blue,
|
backgroundColor: Colors.blue,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -633,7 +633,7 @@ class PengaduanView extends GetView<PetugasDesaController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Status pengaduan berhasil diubah menjadi Selesai',
|
'Status pengaduan berhasil diubah menjadi Selesai',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -547,7 +547,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Alasan penolakan tidak boleh kosong',
|
'Alasan penolakan tidak boleh kosong',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -869,7 +869,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
|
|
||||||
// Pilih kategori bantuan
|
// Pilih kategori bantuan
|
||||||
Text(
|
Text(
|
||||||
'Kategori Bantuan',
|
'Jenis Stok Bantuan',
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -881,7 +881,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12, vertical: 8),
|
horizontal: 12, vertical: 8),
|
||||||
),
|
),
|
||||||
hint: const Text('Pilih kategori bantuan'),
|
hint: const Text('Pilih jenis stok bantuan'),
|
||||||
value: selectedStokBantuanId.value,
|
value: selectedStokBantuanId.value,
|
||||||
items: controller.stokBantuanMap.entries.map((entry) {
|
items: controller.stokBantuanMap.entries.map((entry) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
@ -1288,7 +1288,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Foto bantuan harus diupload',
|
'Foto bantuan harus diupload',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1572,7 +1572,7 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Donatur berhasil ditambahkan',
|
'Donatur berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -4,12 +4,43 @@ import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penya
|
|||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/jadwal_section_widget.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/jadwal_section_widget.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/calendar_view_widget.dart';
|
||||||
|
|
||||||
class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||||
const PenyaluranView({super.key});
|
const PenyaluranView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return DefaultTabController(
|
||||||
|
length: 2,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TabBar(
|
||||||
|
tabs: const [
|
||||||
|
Tab(text: 'Daftar Jadwal'),
|
||||||
|
Tab(text: 'Kalender'),
|
||||||
|
],
|
||||||
|
labelColor: AppTheme.primaryColor,
|
||||||
|
indicatorColor: AppTheme.primaryColor,
|
||||||
|
unselectedLabelColor: Colors.grey,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
children: [
|
||||||
|
// Tab 1: Daftar Jadwal
|
||||||
|
_buildJadwalListView(),
|
||||||
|
|
||||||
|
// Tab 2: Kalender
|
||||||
|
_buildCalendarView(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildJadwalListView() {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () => controller.refreshData(),
|
onRefresh: () => controller.refreshData(),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@ -30,7 +61,7 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Ringkasan jadwal
|
// Ringkasan jadwal
|
||||||
_buildJadwalSummary(context),
|
_buildJadwalSummary(Get.context!),
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
@ -52,7 +83,7 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
// Jadwal mendatang
|
// Jadwal mendatang
|
||||||
JadwalSectionWidget(
|
JadwalSectionWidget(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
title: 'Mendatang',
|
title: '7 Hari Mendatang',
|
||||||
jadwalList: controller.jadwalMendatang,
|
jadwalList: controller.jadwalMendatang,
|
||||||
status: 'Terjadwal',
|
status: 'Terjadwal',
|
||||||
),
|
),
|
||||||
@ -74,6 +105,41 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCalendarView() {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () => controller.refreshData(),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Obx(() {
|
||||||
|
if (controller.isLoading.value) {
|
||||||
|
return const Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(32.0),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Ringkasan jadwal
|
||||||
|
_buildJadwalSummary(Get.context!),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Kalender Penyaluran Bulan Ini
|
||||||
|
CalendarViewWidget(controller: controller),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildJadwalSummary(BuildContext context) {
|
Widget _buildJadwalSummary(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
@ -486,7 +486,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
|||||||
'Permintaan penjadwalan berhasil dikonfirmasi',
|
'Permintaan penjadwalan berhasil dikonfirmasi',
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
@ -494,7 +494,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
|||||||
'Silakan pilih jadwal penyaluran terlebih dahulu',
|
'Silakan pilih jadwal penyaluran terlebih dahulu',
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -554,7 +554,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
|||||||
'Permintaan penjadwalan berhasil ditolak',
|
'Permintaan penjadwalan berhasil ditolak',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
@ -562,7 +562,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
|||||||
'Silakan masukkan alasan penolakan',
|
'Silakan masukkan alasan penolakan',
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -49,7 +49,7 @@ class ProfileController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memuat data profil: ${e.toString()}',
|
'Gagal memuat data profil: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -67,7 +67,7 @@ class ProfileController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Nama tidak boleh kosong',
|
'Nama tidak boleh kosong',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -98,7 +98,7 @@ class ProfileController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Profil berhasil diperbarui',
|
'Profil berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -106,7 +106,7 @@ class ProfileController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memperbarui profil: ${e.toString()}',
|
'Gagal memperbarui profil: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -121,7 +121,7 @@ class ProfileController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Konfirmasi password tidak sesuai',
|
'Konfirmasi password tidak sesuai',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -138,7 +138,7 @@ class ProfileController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Password berhasil diubah',
|
'Password berhasil diubah',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -146,7 +146,7 @@ class ProfileController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengubah password: ${e.toString()}',
|
'Gagal mengubah password: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||||
|
|
||||||
class SupabaseService extends GetxService {
|
class SupabaseService extends GetxService {
|
||||||
static SupabaseService get to => Get.find<SupabaseService>();
|
static SupabaseService get to => Get.find<SupabaseService>();
|
||||||
@ -232,12 +233,16 @@ class SupabaseService extends GetxService {
|
|||||||
final today = DateTime(now.year, now.month, now.day);
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
final tomorrow = today.add(const Duration(days: 1));
|
final tomorrow = today.add(const Duration(days: 1));
|
||||||
|
|
||||||
|
// Konversi ke UTC untuk query ke database
|
||||||
|
final todayUtc = today.toUtc().toIso8601String();
|
||||||
|
final tomorrowUtc = tomorrow.toUtc().toIso8601String();
|
||||||
|
|
||||||
final response = await client
|
final response = await client
|
||||||
.from('penyaluran_bantuan')
|
.from('penyaluran_bantuan')
|
||||||
.select('*')
|
.select('*')
|
||||||
.gte('tanggal_penyaluran', today.toIso8601String())
|
.gte('tanggal_penyaluran', todayUtc)
|
||||||
.lt('tanggal_penyaluran', tomorrow.toIso8601String())
|
.lt('tanggal_penyaluran', tomorrowUtc)
|
||||||
.inFilter('status', ['DISETUJUI', 'BERLANGSUNG']);
|
.inFilter('status', ['DISETUJUI', 'BERLANGSUNG', 'DIJADWALKAN']);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -250,13 +255,18 @@ class SupabaseService extends GetxService {
|
|||||||
try {
|
try {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final today = DateTime(now.year, now.month, now.day);
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final tomorrow = today.add(const Duration(days: 1));
|
||||||
final week = today.add(const Duration(days: 7));
|
final week = today.add(const Duration(days: 7));
|
||||||
|
|
||||||
|
// Konversi ke UTC untuk query ke database
|
||||||
|
final tomorrowUtc = tomorrow.toUtc().toIso8601String();
|
||||||
|
final weekUtc = week.toUtc().toIso8601String();
|
||||||
|
|
||||||
final response = await client
|
final response = await client
|
||||||
.from('penyaluran_bantuan')
|
.from('penyaluran_bantuan')
|
||||||
.select('*')
|
.select('*')
|
||||||
.gte('tanggal_penyaluran', today.toIso8601String())
|
.gte('tanggal_penyaluran', tomorrowUtc)
|
||||||
.lt('tanggal_penyaluran', week.toIso8601String())
|
.lt('tanggal_penyaluran', weekUtc)
|
||||||
.inFilter('status', ['DISETUJUI', 'DIJADWALKAN']);
|
.inFilter('status', ['DISETUJUI', 'DIJADWALKAN']);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@ -333,6 +343,19 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memperbarui status jadwal
|
||||||
|
Future<void> updateJadwalStatus(String jadwalId, String status) async {
|
||||||
|
try {
|
||||||
|
await client.from('penyaluran_bantuan').update({
|
||||||
|
'status': status,
|
||||||
|
'updated_at': DateTime.now().toUtc().toIso8601String(),
|
||||||
|
}).eq('id', jadwalId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating jadwal status: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stok bantuan methods
|
// Stok bantuan methods
|
||||||
Future<List<Map<String, dynamic>>?> getStokBantuan() async {
|
Future<List<Map<String, dynamic>>?> getStokBantuan() async {
|
||||||
try {
|
try {
|
||||||
@ -966,10 +989,7 @@ class SupabaseService extends GetxService {
|
|||||||
// Metode untuk mendapatkan semua lokasi penyaluran
|
// Metode untuk mendapatkan semua lokasi penyaluran
|
||||||
Future<List<Map<String, dynamic>>?> getAllLokasiPenyaluran() async {
|
Future<List<Map<String, dynamic>>?> getAllLokasiPenyaluran() async {
|
||||||
try {
|
try {
|
||||||
final response = await client
|
final response = await client.from('lokasi_penyaluran').select('*');
|
||||||
.from('lokasi_penyaluran')
|
|
||||||
.select('*')
|
|
||||||
.order('nama', ascending: true);
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getting all lokasi penyaluran: $e');
|
print('Error getting all lokasi penyaluran: $e');
|
||||||
@ -977,6 +997,48 @@ class SupabaseService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan daftar penerima penyaluran berdasarkan ID penyaluran
|
||||||
|
Future<List<Map<String, dynamic>>?> getPenerimaPenyaluran(
|
||||||
|
String penyaluranId) async {
|
||||||
|
// Metode ini tidak lagi mengambil data dari database
|
||||||
|
// Gunakan data dummy dari controller
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk memperbarui status penerimaan bantuan
|
||||||
|
Future<bool> updateStatusPenerimaan(int penerimaId, String status,
|
||||||
|
{DateTime? tanggalPenerimaan,
|
||||||
|
String? buktiPenerimaan,
|
||||||
|
String? keterangan}) async {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> updateData = {
|
||||||
|
'status_penerimaan': status,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tanggalPenerimaan != null) {
|
||||||
|
updateData['tanggal_penerimaan'] = tanggalPenerimaan.toIso8601String();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buktiPenerimaan != null) {
|
||||||
|
updateData['bukti_penerimaan'] = buktiPenerimaan;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keterangan != null) {
|
||||||
|
updateData['keterangan'] = keterangan;
|
||||||
|
}
|
||||||
|
|
||||||
|
await client
|
||||||
|
.from('penerima_penyaluran')
|
||||||
|
.update(updateData)
|
||||||
|
.eq('id', penerimaId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating status penerimaan: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan semua kategori bantuan
|
// Metode untuk mendapatkan semua kategori bantuan
|
||||||
Future<List<Map<String, dynamic>>?> getAllKategoriBantuan() async {
|
Future<List<Map<String, dynamic>>?> getAllKategoriBantuan() async {
|
||||||
try {
|
try {
|
||||||
@ -990,4 +1052,74 @@ class SupabaseService extends GetxService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memeriksa koneksi ke Supabase
|
||||||
|
Future<bool> checkConnection() async {
|
||||||
|
try {
|
||||||
|
print('DEBUG SERVICE: Memeriksa koneksi ke Supabase...');
|
||||||
|
|
||||||
|
// Coba melakukan query sederhana
|
||||||
|
final response =
|
||||||
|
await client.from('penerima_penyaluran').select('count').limit(1);
|
||||||
|
|
||||||
|
print('DEBUG SERVICE: Koneksi berhasil, response: $response');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print('DEBUG SERVICE: Error saat memeriksa koneksi: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan data warga berdasarkan ID
|
||||||
|
Future<Map<String, dynamic>?> getWargaById(String wargaId) async {
|
||||||
|
// Metode ini tidak lagi mengambil data dari database
|
||||||
|
// Gunakan data dummy
|
||||||
|
return {
|
||||||
|
'id': wargaId,
|
||||||
|
'nama_lengkap': 'Warga Dummy',
|
||||||
|
'nik': '1234567890123456',
|
||||||
|
'alamat': 'Alamat Dummy',
|
||||||
|
'jenis_kelamin': 'L',
|
||||||
|
'tanggal_lahir': '1990-01-01',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk mencetak struktur data ke konsol
|
||||||
|
void printDataStructure(dynamic data, {String prefix = ''}) {
|
||||||
|
if (data == null) {
|
||||||
|
print('$prefix Data: null');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data is List) {
|
||||||
|
print('$prefix Data adalah List dengan ${data.length} item');
|
||||||
|
if (data.isNotEmpty) {
|
||||||
|
print('$prefix Contoh item pertama:');
|
||||||
|
printDataStructure(data.first, prefix: '$prefix ');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data is Map<String, dynamic>) {
|
||||||
|
print(
|
||||||
|
'$prefix Data adalah Map dengan keys: ${data.keys.toList().join(', ')}');
|
||||||
|
|
||||||
|
// Cek apakah ada key warga
|
||||||
|
if (data.containsKey('warga')) {
|
||||||
|
print('$prefix Data memiliki key "warga"');
|
||||||
|
print('$prefix Tipe data warga: ${data['warga'].runtimeType}');
|
||||||
|
printDataStructure(data['warga'], prefix: '$prefix warga: ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek apakah ada key warga_id
|
||||||
|
if (data.containsKey('warga_id')) {
|
||||||
|
print('$prefix Data memiliki key "warga_id": ${data['warga_id']}');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tipe data lainnya
|
||||||
|
print('$prefix Data: $data (${data.runtimeType})');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
75
lib/app/utils/date_time_helper.dart
Normal file
75
lib/app/utils/date_time_helper.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class DateTimeHelper {
|
||||||
|
/// Mengkonversi DateTime dari UTC ke timezone lokal
|
||||||
|
static DateTime toLocalDateTime(DateTime utcDateTime) {
|
||||||
|
return utcDateTime.toLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format tanggal ke format Indonesia (dd MMM yyyy)
|
||||||
|
static String formatDate(DateTime? dateTime) {
|
||||||
|
if (dateTime == null) return 'Belum ditentukan';
|
||||||
|
|
||||||
|
// Pastikan tanggal dalam timezone lokal
|
||||||
|
final localDateTime = toLocalDateTime(dateTime);
|
||||||
|
return DateFormat('dd MMM yyyy').format(localDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format waktu ke format 24 jam (HH:mm)
|
||||||
|
static String formatTime(DateTime? dateTime) {
|
||||||
|
if (dateTime == null) return 'Belum ditentukan';
|
||||||
|
|
||||||
|
// Pastikan waktu dalam timezone lokal
|
||||||
|
final localDateTime = toLocalDateTime(dateTime);
|
||||||
|
return DateFormat('HH:mm').format(localDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format tanggal dan waktu (dd MMM yyyy HH:mm)
|
||||||
|
static String formatDateTime(DateTime? dateTime) {
|
||||||
|
if (dateTime == null) return 'Belum ditentukan';
|
||||||
|
|
||||||
|
// Pastikan tanggal dan waktu dalam timezone lokal
|
||||||
|
final localDateTime = toLocalDateTime(dateTime);
|
||||||
|
return "${DateFormat('dd MMM yyyy').format(localDateTime)} ${DateFormat('HH:mm').format(localDateTime)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format tanggal lengkap dalam bahasa Indonesia (Senin, 01 Januari 2023)
|
||||||
|
static String formatDateIndonesian(DateTime? dateTime) {
|
||||||
|
if (dateTime == null) return 'Belum ditentukan';
|
||||||
|
|
||||||
|
// Pastikan tanggal dalam timezone lokal
|
||||||
|
final localDateTime = toLocalDateTime(dateTime);
|
||||||
|
|
||||||
|
final List<String> namaBulan = [
|
||||||
|
'Januari',
|
||||||
|
'Februari',
|
||||||
|
'Maret',
|
||||||
|
'April',
|
||||||
|
'Mei',
|
||||||
|
'Juni',
|
||||||
|
'Juli',
|
||||||
|
'Agustus',
|
||||||
|
'September',
|
||||||
|
'Oktober',
|
||||||
|
'November',
|
||||||
|
'Desember'
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<String> namaHari = [
|
||||||
|
'Minggu',
|
||||||
|
'Senin',
|
||||||
|
'Selasa',
|
||||||
|
'Rabu',
|
||||||
|
'Kamis',
|
||||||
|
'Jumat',
|
||||||
|
'Sabtu'
|
||||||
|
];
|
||||||
|
|
||||||
|
final String hari = namaHari[localDateTime.weekday % 7];
|
||||||
|
final String tanggal = localDateTime.day.toString().padLeft(2, '0');
|
||||||
|
final String bulan = namaBulan[localDateTime.month - 1];
|
||||||
|
final String tahun = localDateTime.year.toString();
|
||||||
|
|
||||||
|
return '$hari, $tanggal $bulan $tahun';
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@ import 'package:penyaluran_app/app/services/supabase_service.dart';
|
|||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:syncfusion_localizations/syncfusion_localizations.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -45,6 +47,19 @@ class MyApp extends StatelessWidget {
|
|||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
initialRoute: AppPages.initial,
|
initialRoute: AppPages.initial,
|
||||||
getPages: AppPages.routes,
|
getPages: AppPages.routes,
|
||||||
|
// Konfigurasi locale
|
||||||
|
locale: const Locale('id', 'ID'),
|
||||||
|
fallbackLocale: const Locale('en', 'US'),
|
||||||
|
localizationsDelegates: const [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
SfGlobalLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: const [
|
||||||
|
Locale('id', 'ID'), // Indonesia
|
||||||
|
Locale('en', 'US'), // English
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
pubspec.lock
45
pubspec.lock
@ -190,6 +190,11 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -741,6 +746,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.4"
|
version: "2.8.4"
|
||||||
|
syncfusion_flutter_calendar:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: syncfusion_flutter_calendar
|
||||||
|
sha256: "11b01bc7ad1d240d7c644081bda79e61c0a8d26eec7eba67bfc7274310562897"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "28.2.11"
|
||||||
|
syncfusion_flutter_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: syncfusion_flutter_core
|
||||||
|
sha256: "59b6d2a7deacade6129d2f15615ca49ed56278fea055cd2e52cace78a343dd5e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "28.2.11"
|
||||||
|
syncfusion_flutter_datepicker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: syncfusion_flutter_datepicker
|
||||||
|
sha256: "73ece73742f123c750d674461c6902cbdf32fbd695c15fdf7e8487d290bb7179"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "28.2.11+1"
|
||||||
|
syncfusion_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: syncfusion_localizations
|
||||||
|
sha256: "04bddcd326628ae3aea227d86534b1682e893674df3474fc71c83e0bffb27325"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "28.2.11"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -757,6 +794,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.3"
|
version: "0.7.3"
|
||||||
|
timezone:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timezone
|
||||||
|
sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.0"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -53,7 +53,7 @@ dependencies:
|
|||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
flutter_svg: ^2.0.17
|
flutter_svg: ^2.0.17
|
||||||
|
|
||||||
# Untuk format tanggal dalam bahasa Indonesia
|
# Untuk format tanggal dan waktu
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
|
|
||||||
# HTTP client
|
# HTTP client
|
||||||
@ -64,6 +64,10 @@ dependencies:
|
|||||||
|
|
||||||
# Image picker untuk mengambil gambar dari kamera atau galeri
|
# Image picker untuk mengambil gambar dari kamera atau galeri
|
||||||
image_picker: ^1.0.7
|
image_picker: ^1.0.7
|
||||||
|
syncfusion_flutter_calendar: ^28.2.11
|
||||||
|
syncfusion_localizations: ^28.2.11
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user