Perbarui beberapa file konfigurasi fingerprint untuk arsitektur arm64-v8a, armeabi-v7a, x86, dan x86_64. Modifikasi tampilan dan controller di modul donatur dan petugas desa untuk meningkatkan pengalaman pengguna, termasuk penggantian logika pengambilan data dan penyesuaian tampilan. Hapus kode yang tidak digunakan dan tambahkan fungsionalitas baru untuk mendukung pengelolaan data yang lebih baik.
This commit is contained in:
@ -8,11 +8,16 @@ import 'package:penyaluran_app/app/utils/date_time_helper.dart';
|
||||
|
||||
class CalendarViewWidget extends StatelessWidget {
|
||||
final JadwalPenyaluranController controller;
|
||||
final CalendarController _calendarController = CalendarController();
|
||||
|
||||
const CalendarViewWidget({
|
||||
CalendarViewWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
}) {
|
||||
// Mengatur controller kalender untuk selalu memilih hari ini saat inisialisasi
|
||||
_calendarController.selectedDate = DateTime.now();
|
||||
_calendarController.displayDate = DateTime.now();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -49,6 +54,9 @@ class CalendarViewWidget extends StatelessWidget {
|
||||
child: Obx(() {
|
||||
return SfCalendar(
|
||||
view: CalendarView.month,
|
||||
controller: _calendarController,
|
||||
initialSelectedDate: DateTime.now(),
|
||||
initialDisplayDate: DateTime.now(),
|
||||
dataSource: _getCalendarDataSource(),
|
||||
timeZone: 'Asia/Jakarta',
|
||||
monthViewSettings: MonthViewSettings(
|
||||
@ -246,7 +254,7 @@ class CalendarViewWidget extends StatelessWidget {
|
||||
List<Appointment> appointments = [];
|
||||
|
||||
List<PenyaluranBantuanModel> allJadwal = [
|
||||
...controller.jadwalHariIni,
|
||||
...controller.jadwalAktif,
|
||||
...controller.jadwalMendatang,
|
||||
...controller.jadwalTerlaksana,
|
||||
];
|
||||
@ -556,7 +564,7 @@ class CalendarViewWidget extends StatelessWidget {
|
||||
|
||||
// Cari jadwal dengan ID yang sesuai
|
||||
for (var jadwal in [
|
||||
...controller.jadwalHariIni,
|
||||
...controller.jadwalAktif,
|
||||
...controller.jadwalMendatang,
|
||||
...controller.jadwalTerlaksana
|
||||
]) {
|
||||
|
@ -165,9 +165,10 @@ class JadwalSectionWidget extends StatelessWidget {
|
||||
|
||||
List<PenyaluranBantuanModel> _getCurrentJadwalList() {
|
||||
switch (title) {
|
||||
case 'Hari Ini':
|
||||
return controller.jadwalHariIni.toList();
|
||||
case 'Mendatang':
|
||||
case 'Penyaluran Aktif':
|
||||
return controller.jadwalAktif.toList();
|
||||
|
||||
case '7 Hari Mendatang':
|
||||
return controller.jadwalMendatang.toList();
|
||||
case 'Terlaksana':
|
||||
return controller.jadwalTerlaksana.toList();
|
||||
|
@ -73,17 +73,23 @@ class CounterService extends GetxService {
|
||||
void updatePengaduanCounter(int diproses) {
|
||||
jumlahDiproses.value = diproses;
|
||||
_storage.write(_keyDiproses, diproses);
|
||||
|
||||
print('Counter pengaduan updated and saved - Diproses: $diproses');
|
||||
}
|
||||
|
||||
// Metode untuk memperbarui counter notifikasi
|
||||
void updateNotifikasiCounter(int belumDibaca) {
|
||||
jumlahNotifikasiBelumDibaca.value = belumDibaca;
|
||||
_storage.write(_keyNotifikasi, belumDibaca);
|
||||
|
||||
print('Counter notifikasi updated and saved - Belum Dibaca: $belumDibaca');
|
||||
}
|
||||
|
||||
// Metode untuk memperbarui counter jadwal
|
||||
void updateJadwalCounter(int hariIni) {
|
||||
jumlahJadwalHariIni.value = hariIni;
|
||||
_storage.write(_keyJadwal, hariIni);
|
||||
|
||||
print('Counter jadwal updated and saved - Hari Ini: $hariIni');
|
||||
}
|
||||
}
|
||||
|
@ -204,6 +204,27 @@ class DetailPenyaluranController extends GetxController {
|
||||
.update(updateData)
|
||||
.eq('id', penerima.id!);
|
||||
|
||||
// Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah)
|
||||
final penerimaData = await _supabaseService.client
|
||||
.from('penerima_penyaluran')
|
||||
.select('penyaluran_bantuan_id, stok_bantuan_id, jumlah_bantuan')
|
||||
.eq('id', penerima.id!)
|
||||
.single();
|
||||
|
||||
if (penerimaData != null) {
|
||||
final String stokBantuanId = penerimaData['stok_bantuan_id'];
|
||||
final double jumlah = penerimaData['jumlah_bantuan'] is int
|
||||
? penerimaData['jumlah_bantuan'].toDouble()
|
||||
: penerimaData['jumlah_bantuan'];
|
||||
|
||||
// Kurangi stok dan catat riwayat
|
||||
final petugasId = _supabaseService.client.auth.currentUser?.id;
|
||||
if (petugasId != null) {
|
||||
await _supabaseService.kurangiStokDariPenyaluran(
|
||||
penerima.id!, stokBantuanId, jumlah, petugasId);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh data setelah konfirmasi berhasil
|
||||
await refreshData();
|
||||
|
||||
|
@ -24,7 +24,7 @@ class JadwalPenyaluranController extends GetxController {
|
||||
final RxInt selectedCategoryIndex = 0.obs;
|
||||
|
||||
// Data untuk jadwal
|
||||
final RxList<PenyaluranBantuanModel> jadwalHariIni =
|
||||
final RxList<PenyaluranBantuanModel> jadwalAktif =
|
||||
<PenyaluranBantuanModel>[].obs;
|
||||
final RxList<PenyaluranBantuanModel> jadwalMendatang =
|
||||
<PenyaluranBantuanModel>[].obs;
|
||||
@ -97,7 +97,7 @@ class JadwalPenyaluranController extends GetxController {
|
||||
List<PenyaluranBantuanModel> jadwalToUpdate = [];
|
||||
List<PenyaluranBantuanModel> jadwalTerlewat = [];
|
||||
|
||||
for (var jadwal in jadwalHariIni) {
|
||||
for (var jadwal in jadwalAktif) {
|
||||
if (jadwal.tanggalPenyaluran != null) {
|
||||
final jadwalDateTime =
|
||||
DateTimeHelper.toLocalDateTime(jadwal.tanggalPenyaluran!);
|
||||
@ -175,9 +175,9 @@ class JadwalPenyaluranController extends GetxController {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
// Mengambil data jadwal hari ini
|
||||
final jadwalHariIniData = await _supabaseService.getJadwalHariIni();
|
||||
if (jadwalHariIniData != null) {
|
||||
jadwalHariIni.value = jadwalHariIniData
|
||||
final jadwalAktifData = await _supabaseService.getJadwalAktif();
|
||||
if (jadwalAktifData != null) {
|
||||
jadwalAktif.value = jadwalAktifData
|
||||
.map((data) => PenyaluranBantuanModel.fromJson(data))
|
||||
.toList();
|
||||
}
|
||||
|
@ -250,29 +250,29 @@ class PelaksanaanPenyaluranController extends GetxController {
|
||||
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);
|
||||
// // 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);
|
||||
|
||||
// Jika berhasil, perbarui data lokal
|
||||
if (result) {
|
||||
await loadPenerimaPenyaluran(activePenyaluranId.value);
|
||||
}
|
||||
// // Jika berhasil, perbarui data lokal
|
||||
// if (result) {
|
||||
// await loadPenerimaPenyaluran(activePenyaluranId.value);
|
||||
// }
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
print('Error updating status penerimaan: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// return result;
|
||||
// } catch (e) {
|
||||
// print('Error updating status penerimaan: $e');
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Metode untuk menyelesaikan jadwal penyaluran
|
||||
Future<void> completeJadwal(String jadwalId) async {
|
||||
|
@ -309,10 +309,10 @@ class PetugasDesaController extends GetxController {
|
||||
// Metode untuk memuat data jadwal
|
||||
Future<void> loadJadwalData() async {
|
||||
try {
|
||||
final jadwalHariIniData = await _supabaseService.getJadwalHariIni();
|
||||
if (jadwalHariIniData != null) {
|
||||
jadwalHariIni.value = jadwalHariIniData;
|
||||
_counterService.updateJadwalCounter(jadwalHariIniData.length);
|
||||
final jadwalAktifData = await _supabaseService.getJadwalAktif();
|
||||
if (jadwalAktifData != null) {
|
||||
jadwalHariIni.value = jadwalAktifData;
|
||||
_counterService.updateJadwalCounter(jadwalAktifData.length);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error loading jadwal data: $e');
|
||||
@ -360,7 +360,7 @@ class PetugasDesaController extends GetxController {
|
||||
|
||||
// Hitung jumlah pengaduan dengan status DIPROSES
|
||||
for (var item in pengaduanData) {
|
||||
if (item['status'] == 'DIPROSES') {
|
||||
if (item['status'] == 'MENUNGGU') {
|
||||
diproses++;
|
||||
}
|
||||
}
|
||||
@ -609,22 +609,22 @@ class PetugasDesaController extends GetxController {
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
|
@ -4,10 +4,12 @@ import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||
import 'package:penyaluran_app/app/data/models/notifikasi_model.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/modules/petugas_desa/controllers/counter_service.dart';
|
||||
|
||||
class PetugasDesaDashboardController extends GetxController {
|
||||
final AuthController _authController = Get.find<AuthController>();
|
||||
final SupabaseService _supabaseService = SupabaseService.to;
|
||||
late final CounterService _counterService;
|
||||
|
||||
final RxBool isLoading = false.obs;
|
||||
|
||||
@ -22,6 +24,12 @@ class PetugasDesaDashboardController extends GetxController {
|
||||
final RxInt totalPenitipanTerverifikasi = 0.obs;
|
||||
final RxDouble progressPenyaluran = 0.0.obs;
|
||||
|
||||
// Data untuk status penyaluran
|
||||
final RxInt penyaluranDijadwalkan = 0.obs;
|
||||
final RxInt penyaluranAktif = 0.obs;
|
||||
final RxInt penyaluranBatal = 0.obs;
|
||||
final RxInt penyaluranTerlaksana = 0.obs;
|
||||
|
||||
// Data untuk notifikasi
|
||||
final RxList<NotifikasiModel> notifikasiBelumDibaca = <NotifikasiModel>[].obs;
|
||||
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
||||
@ -45,13 +53,24 @@ class PetugasDesaDashboardController extends GetxController {
|
||||
userProfile['desa']?['nama'] ??
|
||||
(userProfile['desa_id'] != null ? 'Desa' : 'Desa');
|
||||
|
||||
// Getter untuk counter dari CounterService
|
||||
RxInt get jumlahMenunggu => _counterService.jumlahMenunggu;
|
||||
RxInt get jumlahDiproses => _counterService.jumlahDiproses;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// Inisialisasi CounterService jika belum ada
|
||||
if (!Get.isRegistered<CounterService>()) {
|
||||
Get.put(CounterService(), permanent: true);
|
||||
}
|
||||
_counterService = Get.find<CounterService>();
|
||||
|
||||
loadUserProfile();
|
||||
loadDashboardData();
|
||||
loadNotifikasiData();
|
||||
loadJadwalHariIni();
|
||||
loadJadwalAktif();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -97,6 +116,15 @@ class PetugasDesaDashboardController extends GetxController {
|
||||
await _supabaseService.getTotalSemuaPenyaluran();
|
||||
totalSemuaPenyaluran.value = semuaPenyaluranData ?? 0;
|
||||
|
||||
// Mengambil data status penyaluran
|
||||
final statusPenyaluranData = await _supabaseService.getStatusPenyaluran();
|
||||
if (statusPenyaluranData != null) {
|
||||
penyaluranDijadwalkan.value = statusPenyaluranData['dijadwalkan'] ?? 0;
|
||||
penyaluranAktif.value = statusPenyaluranData['aktif'] ?? 0;
|
||||
penyaluranBatal.value = statusPenyaluranData['batal'] ?? 0;
|
||||
penyaluranTerlaksana.value = statusPenyaluranData['terlaksana'] ?? 0;
|
||||
}
|
||||
|
||||
// Menghitung progress penyaluran (persentase penyaluran yang terlaksana dari total semua penyaluran)
|
||||
if (totalSemuaPenyaluran.value > 0) {
|
||||
progressPenyaluran.value =
|
||||
@ -127,9 +155,9 @@ class PetugasDesaDashboardController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadJadwalHariIni() async {
|
||||
Future<void> loadJadwalAktif() async {
|
||||
try {
|
||||
final jadwalData = await _supabaseService.getJadwalHariIni();
|
||||
final jadwalData = await _supabaseService.getJadwalAktif();
|
||||
if (jadwalData != null) {
|
||||
jadwalHariIni.value = jadwalData;
|
||||
}
|
||||
@ -145,7 +173,7 @@ class PetugasDesaDashboardController extends GetxController {
|
||||
loadUserProfile(),
|
||||
loadDashboardData(),
|
||||
loadNotifikasiData(),
|
||||
loadJadwalHariIni(),
|
||||
loadJadwalAktif(),
|
||||
]);
|
||||
} catch (e) {
|
||||
print('Error refreshing data: $e');
|
||||
|
@ -302,4 +302,26 @@ class RiwayatStokController extends GetxController {
|
||||
alasan.value = '';
|
||||
fotoBukti.value = null;
|
||||
}
|
||||
|
||||
// Metode untuk mendapatkan detail referensi berdasarkan id dan sumber
|
||||
Future<Map<String, dynamic>?> getReferensiDetail({
|
||||
required String idReferensi,
|
||||
required String sumber,
|
||||
}) async {
|
||||
try {
|
||||
Map<String, dynamic>? data;
|
||||
|
||||
// Berdasarkan sumber, ambil data dari tabel yang sesuai
|
||||
if (sumber == 'penitipan') {
|
||||
data = await _supabaseService.getPenitipanById(idReferensi);
|
||||
} else if (sumber == 'penerimaan') {
|
||||
data = await _supabaseService.getPenerimaanById(idReferensi);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
print('Error getting referensi detail: $e');
|
||||
throw Exception('Gagal mendapatkan data: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FutureBuilder<List<Map<String, dynamic>>?>(
|
||||
future: SupabaseService.to.getJadwalHariIni(),
|
||||
future: SupabaseService.to.getJadwalAktif(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@ -153,11 +153,16 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
||||
}
|
||||
|
||||
Widget _buildProgressPenyaluran() {
|
||||
// Menghitung nilai untuk progress
|
||||
final terlaksana = controller.totalPenyaluran.value;
|
||||
final total = controller.totalSemuaPenyaluran.value;
|
||||
final progressValue = total > 0 ? terlaksana / total : 0.0;
|
||||
final belumTerlaksana = total - terlaksana;
|
||||
// Menghitung nilai untuk progress berdasarkan status
|
||||
final terlaksana = controller.penyaluranTerlaksana.value;
|
||||
final batal = controller.penyaluranBatal.value;
|
||||
final dijadwalkan = controller.penyaluranDijadwalkan.value;
|
||||
final aktif = controller.penyaluranAktif.value;
|
||||
|
||||
final total = terlaksana + batal + dijadwalkan + aktif;
|
||||
final progressValue = total > 0 ? (terlaksana + batal) / total : 0.0;
|
||||
final belumTerlaksana = dijadwalkan +
|
||||
aktif; // Yang belum terlaksana adalah yang dijadwalkan dan aktif
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@ -214,6 +219,12 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
||||
Colors.white.withOpacity(0.7),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildProgressDetailItem(
|
||||
'Dibatalkan',
|
||||
'$batal',
|
||||
Colors.white.withOpacity(0.7),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildProgressDetailItem(
|
||||
'Total Penyaluran',
|
||||
'$total',
|
||||
@ -270,18 +281,22 @@ class DashboardView extends GetView<PetugasDesaDashboardController> {
|
||||
Expanded(
|
||||
child: StatisticCard(
|
||||
title: 'Penitipan',
|
||||
count: controller.jumlahNotifikasiBelumDibaca.toString(),
|
||||
count: controller.jumlahMenunggu.value.toString(),
|
||||
subtitle: 'Perlu Konfirmasi',
|
||||
height: 120,
|
||||
icon: Icons.inbox,
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.orange, Colors.deepOrange],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: StatisticCard(
|
||||
title: 'Pengaduan',
|
||||
count:
|
||||
'${controller.totalPenerima.value > 0 ? controller.totalPenerima.value ~/ 10 : 0}',
|
||||
count: controller.jumlahDiproses.value.toString(),
|
||||
subtitle: 'Perlu Tindakan',
|
||||
height: 120,
|
||||
gradient: LinearGradient(
|
||||
|
@ -698,10 +698,7 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
|
||||
try {
|
||||
imageUrl =
|
||||
await controller.uploadBuktiPenerimaan(_buktiPenerimaan!.path);
|
||||
print('Berhasil upload bukti penerimaan: $imageUrl');
|
||||
} catch (e) {
|
||||
// Jika upload bukti penerimaan gagal, tampilkan pesan dan hentikan proses
|
||||
print('Error upload bukti penerimaan: $e');
|
||||
throw Exception('Gagal mengupload bukti penerimaan: $e');
|
||||
}
|
||||
|
||||
@ -711,46 +708,31 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
|
||||
signatureFile = File('${tempDir.path}/signature.png');
|
||||
await signatureFile.writeAsBytes(_signatureImage!);
|
||||
|
||||
print('Signature file path: ${signatureFile.path}');
|
||||
print('Signature file exists: ${signatureFile.existsSync()}');
|
||||
print('Signature file size: ${signatureFile.lengthSync()} bytes');
|
||||
|
||||
signatureUrl = await controller.uploadBuktiPenerimaan(
|
||||
signatureFile.path,
|
||||
isTandaTangan: true,
|
||||
);
|
||||
print('Berhasil upload tanda tangan: $signatureUrl');
|
||||
} catch (e) {
|
||||
// Jika upload tanda tangan gagal, tampilkan pesan dan hentikan proses
|
||||
print('Error upload tanda tangan: $e');
|
||||
throw Exception('Gagal mengupload tanda tangan: $e');
|
||||
}
|
||||
|
||||
// Konfirmasi penerimaan
|
||||
try {
|
||||
print('Melakukan konfirmasi penerimaan untuk ID: ${penerima.id}');
|
||||
await controller.konfirmasiPenerimaan(
|
||||
penerima,
|
||||
buktiPenerimaan: imageUrl,
|
||||
tandaTangan: signatureUrl,
|
||||
);
|
||||
print('Konfirmasi penerimaan berhasil');
|
||||
} catch (e) {
|
||||
// Jika konfirmasi penerimaan gagal, tampilkan pesan dan hentikan proses
|
||||
print('Error konfirmasi penerimaan: $e');
|
||||
throw Exception('Gagal melakukan konfirmasi penerimaan: $e');
|
||||
}
|
||||
|
||||
// Hapus file sementara sebelum navigasi
|
||||
try {
|
||||
if (signatureFile.existsSync()) {
|
||||
await signatureFile.delete();
|
||||
}
|
||||
if (tempDir.existsSync()) {
|
||||
await tempDir.delete();
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error saat menghapus file sementara: $e');
|
||||
if (signatureFile.existsSync()) {
|
||||
await signatureFile.delete();
|
||||
}
|
||||
if (tempDir.existsSync()) {
|
||||
await tempDir.delete();
|
||||
}
|
||||
|
||||
// Tutup semua snackbar yang mungkin masih terbuka
|
||||
|
@ -323,144 +323,324 @@ class PengaduanView extends GetView<PengaduanController> {
|
||||
formattedDate = DateTimeHelper.formatDate(item.tanggalPengaduan);
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(26),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
side: BorderSide(
|
||||
color: statusColor.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
elevation: 3,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.warga?['nama'] ?? item.judul ?? '',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
// Header dengan warna sesuai status
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
statusIcon,
|
||||
size: 16,
|
||||
color: statusColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
item.status ?? '',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: statusColor,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.report_problem,
|
||||
color: statusColor,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
item.warga?['nama'] ?? item.judul ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: statusColor,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: statusColor,
|
||||
width: 1.0,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
statusIcon,
|
||||
size: 14,
|
||||
color: statusColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
item.status ?? '',
|
||||
style: TextStyle(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Deskripsi masalah
|
||||
if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade200,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Deskripsi Masalah:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
item.deskripsi ?? '',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Informasi penyaluran bantuan jika ada
|
||||
if (item.penerimaPenyaluran != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: Colors.blue.shade200,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Penyaluran: ${item.namaPenyaluran ?? "Tidak tersedia"}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Bantuan',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.stokBantuan?['nama'] ?? '-',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Jumlah',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${item.jumlahBantuan} ${item.stokBantuan?['satuan'] ?? ''}',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Informasi pelapor dan tanggal
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Pelapor',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.warga?['nama_lengkap'] ?? '-',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'NIK',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.warga?['nik'] ?? '-',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
item.deskripsi ?? '',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.person,
|
||||
label: 'Pelapor',
|
||||
value: item.warga?['nama_lengkap'] ?? '',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.numbers,
|
||||
label: 'NIK',
|
||||
value: item.warga?['nik'] ?? '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (item.penerimaPenyaluran != null) ...[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.shopping_bag,
|
||||
label: 'Jumlah',
|
||||
value:
|
||||
'${item.jumlahBantuan} ${item.stokBantuan['satuan']}',
|
||||
)),
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.inventory,
|
||||
label: 'Stok Bantuan',
|
||||
value: item.stokBantuan['nama'] ?? '',
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Informasi tanggal
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
size: 12,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
formattedDate,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Tombol detail
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Get.toNamed('/detail-pengaduan',
|
||||
arguments: {'id': item.id});
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Lihat Detail'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: statusColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 8),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.category,
|
||||
label: 'Nama Penyaluran',
|
||||
value: item.namaPenyaluran ?? '',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.calendar_today,
|
||||
label: 'Tanggal Pengaduan',
|
||||
value: formattedDate,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: _buildActionButtons(context, item),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -505,174 +685,19 @@ class PengaduanView extends GetView<PengaduanController> {
|
||||
}
|
||||
|
||||
List<Widget> _buildActionButtons(BuildContext context, dynamic item) {
|
||||
final status = item.status?.toUpperCase();
|
||||
|
||||
if (status == 'MENUNGGU') {
|
||||
return [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi untuk memproses pengaduan
|
||||
_showTindakanDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.engineering, size: 18),
|
||||
label: const Text('Tindakan'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
return [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Navigasi ke halaman detail pengaduan
|
||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Navigasi ke halaman detail pengaduan
|
||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (status == 'TINDAKAN') {
|
||||
return [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi untuk menyelesaikan pengaduan
|
||||
_showSelesaikanDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.check_circle, size: 18),
|
||||
label: const Text('Selesaikan'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.green,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Navigasi ke halaman detail pengaduan
|
||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Navigasi ke halaman detail pengaduan
|
||||
Get.toNamed('/detail-pengaduan', arguments: {'id': item.id});
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
void _showTindakanDialog(BuildContext context, dynamic item) {
|
||||
controller.tindakanController.clear();
|
||||
controller.catatanController.clear();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Tindakan Pengaduan'),
|
||||
content: Form(
|
||||
key: controller.tindakanFormKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: controller.tindakanController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Tindakan yang dilakukan',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 3,
|
||||
validator: controller.validateTindakan,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: controller.catatanController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Catatan (opsional)',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (controller.tindakanFormKey.currentState!.validate()) {
|
||||
Navigator.pop(context);
|
||||
controller.tambahTindakanPengaduan(
|
||||
pengaduanId: item.id!,
|
||||
tindakan: controller.tindakanController.text,
|
||||
kategoriTindakan: 'VERIFIKASI_DATA',
|
||||
statusTindakan: 'PROSES',
|
||||
catatan: controller.catatanController.text.isEmpty
|
||||
? null
|
||||
: controller.catatanController.text,
|
||||
buktiTindakanPaths: [],
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Simpan'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSelesaikanDialog(BuildContext context, dynamic item) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Selesaikan Pengaduan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Pengaduan dari: ${item.warga?['nama'] ?? ''}'),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Apakah Anda yakin ingin menyelesaikan pengaduan ini?',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
controller.selesaikanPengaduan(item.id!);
|
||||
},
|
||||
child: const Text('Selesaikan'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -306,236 +306,443 @@ class PenitipanView extends GetView<PenitipanBantuanController> {
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(26),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color: statusColor.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
// Header dengan status
|
||||
Container(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
donaturNama,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
statusIcon,
|
||||
size: 16,
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
if (isDonaturManual)
|
||||
Tooltip(
|
||||
message: 'Donatur Manual (Diinput oleh petugas desa)',
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: Colors.blue.shade300),
|
||||
),
|
||||
child: const Text(
|
||||
'Manual',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
statusIcon,
|
||||
size: 16,
|
||||
color: statusColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
item.status ?? 'Tidak diketahui',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: isUang ? Icons.monetization_on : Icons.category,
|
||||
label: 'Kategori Bantuan',
|
||||
value: kategoriNama,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon:
|
||||
isUang ? Icons.account_balance_wallet : Icons.inventory,
|
||||
label: 'Jumlah',
|
||||
value: isUang
|
||||
? 'Rp ${DateTimeHelper.formatNumber(item.jumlah)}'
|
||||
: '${DateTimeHelper.formatNumber(item.jumlah)} $kategoriSatuan',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.calendar_today,
|
||||
label: 'Tanggal Dibuat',
|
||||
value: DateTimeHelper.formatDateTime(item.createdAt,
|
||||
defaultValue: 'Tidak ada tanggal'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: item.status == 'TERVERIFIKASI' &&
|
||||
item.petugasDesaId != null
|
||||
? _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.person,
|
||||
label: 'Diverifikasi Oleh',
|
||||
value:
|
||||
controller.getPetugasDesaNama(item.petugasDesaId),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Tampilkan thumbnail foto bantuan jika ada
|
||||
if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
size: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Foto Bantuan',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'(${item.fotoBantuan!.length} foto)',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
DateTimeHelper.formatDate(item.createdAt),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey.shade700,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
if (item.status == 'MENUNGGU')
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
// Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
_showVerifikasiDialog(context, item.id ?? '');
|
||||
},
|
||||
icon: const Icon(Icons.check, size: 18),
|
||||
label: const Text('Terima'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.green,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
// Donatur info
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: AppTheme.primaryColor.withOpacity(0.1),
|
||||
radius: 20,
|
||||
child: Text(
|
||||
donaturNama.substring(0, 1).toUpperCase(),
|
||||
style: TextStyle(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
donaturNama,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (isDonaturManual)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: Colors.blue.shade300),
|
||||
),
|
||||
child: const Text(
|
||||
'Manual',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'Donatur',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
_showTolakDialog(context, item.id ?? '');
|
||||
},
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
label: const Text('Tolak'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Informasi bantuan
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isUang
|
||||
? Colors.green.withOpacity(0.1)
|
||||
: Colors.blue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isUang
|
||||
? Icons.monetization_on
|
||||
: Icons.category,
|
||||
size: 16,
|
||||
color: isUang ? Colors.green : Colors.blue,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Kategori',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(
|
||||
color: isUang
|
||||
? Colors.green
|
||||
: Colors.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
kategoriNama,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isUang
|
||||
? Colors.amber.withOpacity(0.1)
|
||||
: Colors.purple.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isUang
|
||||
? Icons.account_balance_wallet
|
||||
: Icons.inventory,
|
||||
size: 16,
|
||||
color: isUang
|
||||
? Colors.amber.shade800
|
||||
: Colors.purple,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Jumlah',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(
|
||||
color: isUang
|
||||
? Colors.amber.shade800
|
||||
: Colors.purple,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
isUang
|
||||
? 'Rp ${DateTimeHelper.formatNumber(item.jumlah)}'
|
||||
: '${DateTimeHelper.formatNumber(item.jumlah)} $kategoriSatuan',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
_showDetailDialog(context, item, donaturNama);
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
|
||||
// Tampilkan thumbnail foto bantuan jika ada
|
||||
if (item.fotoBantuan != null && item.fotoBantuan!.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
size: 16,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Foto Bantuan',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
color: Colors.grey.shade700,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${item.fotoBantuan!.length} foto',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (item.status == 'TERVERIFIKASI' &&
|
||||
item.petugasDesaId != null)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.verified_user,
|
||||
size: 16,
|
||||
color: Colors.green,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Diverifikasi oleh ',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade700),
|
||||
),
|
||||
TextSpan(
|
||||
text: controller.getPetugasDesaNama(
|
||||
item.petugasDesaId),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Footer dengan tombol aksi
|
||||
if (item.status == 'MENUNGGU')
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
border: Border(
|
||||
top: BorderSide(color: Colors.grey.shade200),
|
||||
),
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
_showDetailDialog(context, item, donaturNama);
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 16),
|
||||
label: const Text('Detail'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
side: BorderSide(color: Colors.blue.shade300),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
_showTolakDialog(context, item.id ?? '');
|
||||
},
|
||||
icon: const Icon(Icons.close, size: 16),
|
||||
label: const Text('Tolak'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
side: BorderSide(color: Colors.red.shade300),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_showVerifikasiDialog(context, item.id ?? '');
|
||||
},
|
||||
icon: const Icon(Icons.check, size: 16),
|
||||
label: const Text('Terima'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
_showDetailDialog(context, item, donaturNama);
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
border: Border(
|
||||
top: BorderSide(color: Colors.grey.shade200),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_showDetailDialog(context, item, donaturNama);
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 16),
|
||||
label: const Text('Lihat Detail'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -75,8 +75,8 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
// Jadwal hari ini
|
||||
JadwalSectionWidget(
|
||||
controller: controller,
|
||||
title: 'Hari Ini',
|
||||
jadwalList: controller.jadwalHariIni,
|
||||
title: 'Penyaluran Aktif',
|
||||
jadwalList: controller.jadwalAktif,
|
||||
status: 'Aktif',
|
||||
),
|
||||
|
||||
@ -158,7 +158,7 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
context,
|
||||
icon: Icons.event_available,
|
||||
title: 'Aktif',
|
||||
value: '${controller.jadwalHariIni.length}',
|
||||
value: '${controller.jadwalAktif.length}',
|
||||
color: Colors.green,
|
||||
)),
|
||||
),
|
||||
|
@ -343,9 +343,6 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
activeIcon: Icons.warning_amber,
|
||||
title: 'Pengaduan',
|
||||
isSelected: controller.activeTabIndex.value == 3,
|
||||
badge: controller.jumlahDiproses.value > 0
|
||||
? controller.jumlahDiproses.value.toString()
|
||||
: null,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
controller.changeTab(3);
|
||||
@ -675,7 +672,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.red,
|
||||
color: Colors.orange,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
@ -705,7 +702,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.red,
|
||||
color: Colors.orange,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -332,176 +332,283 @@ class StokBantuanView extends GetView<StokBantuanController> {
|
||||
}
|
||||
|
||||
Widget _buildStokBantuanItem(BuildContext context, StokBantuanModel item) {
|
||||
// Tentukan warna berdasarkan jenis bantuan
|
||||
Color categoryColor =
|
||||
item.isUang == true ? Colors.amber.shade700 : AppTheme.primaryColor;
|
||||
|
||||
// Cek apakah stok hampir habis (kurang dari 10)
|
||||
bool isLowStock = !item.isUang! && item.totalStok! < 10;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(26),
|
||||
color: Colors.grey.withAlpha(30),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.nama ?? 'Tanpa Nama',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header dengan gradient berdasarkan jenis bantuan
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
categoryColor.withOpacity(0.8),
|
||||
categoryColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.nama ?? 'Tanpa Nama',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
item.isUang == true
|
||||
? Icons.monetization_on
|
||||
: Icons.inventory_2_outlined,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
item.kategoriBantuan != null
|
||||
? (item.kategoriBantuan!['nama'] ??
|
||||
'Tidak Ada Kategori')
|
||||
: 'Tidak Ada Kategori',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Body content
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Deskripsi
|
||||
if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
item.deskripsi!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
// Detail stok/dana dalam card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isLowStock
|
||||
? Colors.red.shade50
|
||||
: (item.isUang == true
|
||||
? Colors.amber.shade50
|
||||
: Colors.blue.shade50),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: isLowStock
|
||||
? Colors.red.shade200
|
||||
: (item.isUang == true
|
||||
? Colors.amber.shade200
|
||||
: Colors.blue.shade200),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
item.isUang == true
|
||||
? Icons.monetization_on
|
||||
: (isLowStock
|
||||
? Icons.warning_amber_rounded
|
||||
: Icons.inventory),
|
||||
size: 20,
|
||||
color: isLowStock
|
||||
? Colors.red.shade800
|
||||
: (item.isUang == true
|
||||
? Colors.amber.shade800
|
||||
: Colors.blue.shade800),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.isUang == true
|
||||
? 'Total Dana'
|
||||
: (isLowStock
|
||||
? 'Stok Hampir Habis!'
|
||||
: 'Total Stok'),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isLowStock
|
||||
? Colors.red.shade800
|
||||
: (item.isUang == true
|
||||
? Colors.amber.shade800
|
||||
: Colors.blue.shade800),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.isUang == true
|
||||
? 'Rp ${DateTimeHelper.formatNumber(item.totalStok)}'
|
||||
: '${DateTimeHelper.formatNumber(item.totalStok)} ${item.satuan ?? ''}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isLowStock
|
||||
? Colors.red.shade900
|
||||
: (item.isUang == true
|
||||
? Colors.amber.shade900
|
||||
: Colors.blue.shade900),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.isUang == true)
|
||||
const Icon(
|
||||
Icons.monetization_on,
|
||||
size: 16,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
if (item.isUang == true) const SizedBox(width: 4),
|
||||
Text(
|
||||
item.kategoriBantuan != null
|
||||
? (item.kategoriBantuan!['nama'] ??
|
||||
'Tidak Ada Kategori')
|
||||
: 'Tidak Ada Kategori',
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Additional details
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.access_time,
|
||||
size: 16,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.updatedAt != null
|
||||
? 'Diperbarui: ${DateTimeHelper.formatDateTimeWithHour(item.updatedAt!)}'
|
||||
: 'Tidak ada data pembaruan',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Tombol Aksi
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
border: Border(
|
||||
top: BorderSide(color: Colors.grey.shade200),
|
||||
),
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// Tampilkan dialog edit stok bantuan
|
||||
_showEditStokDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.edit_outlined, size: 16),
|
||||
label: const Text('Edit'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
side: BorderSide(color: Colors.blue.shade300),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// Tampilkan dialog konfirmasi hapus
|
||||
_showDeleteConfirmation(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outline, size: 16),
|
||||
label: const Text('Hapus'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
side: BorderSide(color: Colors.red.shade300),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text(
|
||||
item.deskripsi!,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: item.isUang == true
|
||||
? Icons.monetization_on
|
||||
: Icons.inventory,
|
||||
label: item.isUang == true ? 'Total Dana' : 'Total Stok',
|
||||
value: item.isUang == true
|
||||
? 'Rp ${DateTimeHelper.formatNumber(item.totalStok)}'
|
||||
: '${DateTimeHelper.formatNumber(item.totalStok)} ${item.satuan ?? ''}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildItemDetail(
|
||||
context,
|
||||
icon: Icons.access_time,
|
||||
label: 'Terakhir Diperbarui',
|
||||
value: item.updatedAt != null
|
||||
? '${item.updatedAt!.day}/${item.updatedAt!.month}/${item.updatedAt!.year} ${item.updatedAt!.hour}:${item.updatedAt!.minute}'
|
||||
: 'Tidak ada data',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Tampilkan dialog edit stok bantuan
|
||||
_showEditStokDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.edit_outlined, size: 18),
|
||||
label: const Text('Edit'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Tampilkan dialog konfirmasi hapus
|
||||
_showDeleteConfirmation(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outline, size: 18),
|
||||
label: const Text('Hapus'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemDetail(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required String value,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Skema Bantuan
|
||||
Text(
|
||||
@ -175,6 +175,18 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
}
|
||||
|
||||
await loadPengajuanKelayakan(value);
|
||||
|
||||
// Periksa apakah ada penerima
|
||||
if (jumlahPenerima.value == 0) {
|
||||
Get.snackbar(
|
||||
'Perhatian',
|
||||
'Skema bantuan ini tidak memiliki penerima yang terverifikasi!',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
@ -184,6 +196,37 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
return null;
|
||||
},
|
||||
)),
|
||||
|
||||
// const SizedBox(height: 16),
|
||||
// Pesan pemberitahuan jika tidak ada penerima
|
||||
Obx(() => jumlahPenerima.value == 0 &&
|
||||
selectedSkemaBantuanId.value != null
|
||||
? Container(
|
||||
margin: const EdgeInsets.only(top: 16),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.red.shade200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.warning_amber_rounded,
|
||||
color: Colors.red.shade700),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Skema bantuan ini tidak memiliki penerima yang terverifikasi. Tambahkan penerima terlebih dahulu.',
|
||||
style: TextStyle(
|
||||
color: Colors.red.shade700,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox()),
|
||||
const SizedBox(height: 16),
|
||||
// Jumlah Penerima (Otomatis)
|
||||
Row(
|
||||
@ -755,67 +798,78 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
// Tombol Submit
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (formKey.currentState!.validate()) {
|
||||
// Periksa kecukupan stok
|
||||
if (!isStokCukup.value) {
|
||||
Get.snackbar(
|
||||
'Stok Tidak Cukup',
|
||||
'Stok bantuan tidak mencukupi untuk penyaluran ini. Silakan tambah stok terlebih dahulu.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
return;
|
||||
}
|
||||
child: Obx(() => ElevatedButton(
|
||||
onPressed: jumlahPenerima.value > 0
|
||||
? () {
|
||||
if (formKey.currentState!.validate()) {
|
||||
// Periksa kecukupan stok
|
||||
if (!isStokCukup.value) {
|
||||
Get.snackbar(
|
||||
'Stok Tidak Cukup',
|
||||
'Stok bantuan tidak mencukupi untuk penyaluran ini. Silakan tambah stok terlebih dahulu.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
duration: const Duration(seconds: 4),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Gabungkan tanggal dan waktu mulai
|
||||
DateTime? tanggalWaktuMulai;
|
||||
if (selectedDate.value != null &&
|
||||
selectedWaktuMulai.value != null) {
|
||||
tanggalWaktuMulai = DateTime(
|
||||
selectedDate.value!.year,
|
||||
selectedDate.value!.month,
|
||||
selectedDate.value!.day,
|
||||
selectedWaktuMulai.value!.hour,
|
||||
selectedWaktuMulai.value!.minute,
|
||||
).toLocal();
|
||||
}
|
||||
// Gabungkan tanggal dan waktu mulai
|
||||
DateTime? tanggalWaktuMulai;
|
||||
if (selectedDate.value != null &&
|
||||
selectedWaktuMulai.value != null) {
|
||||
tanggalWaktuMulai = DateTime(
|
||||
selectedDate.value!.year,
|
||||
selectedDate.value!.month,
|
||||
selectedDate.value!.day,
|
||||
selectedWaktuMulai.value!.hour,
|
||||
selectedWaktuMulai.value!.minute,
|
||||
).toLocal();
|
||||
}
|
||||
|
||||
// Panggil fungsi untuk menambahkan penyaluran
|
||||
controller.tambahPenyaluran(
|
||||
nama: namaController.text,
|
||||
deskripsi: deskripsiController.text,
|
||||
skemaId: selectedSkemaBantuanId.value!,
|
||||
lokasiPenyaluranId: selectedLokasiPenyaluranId.value!,
|
||||
jumlahPenerima: jumlahPenerima.value,
|
||||
tanggalPenyaluran: tanggalWaktuMulai,
|
||||
kategoriBantuanId:
|
||||
selectedSkemaBantuan.value!.kategoriBantuanId!,
|
||||
jumlahDiterimaPerOrang: jumlahDiterimaPerOrang.value,
|
||||
stokBantuanId:
|
||||
selectedSkemaBantuan.value!.stokBantuanId!,
|
||||
totalStokDibutuhkan: totalStokDibutuhkan.value);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Simpan Penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Panggil fungsi untuk menambahkan penyaluran
|
||||
controller.tambahPenyaluran(
|
||||
nama: namaController.text,
|
||||
deskripsi: deskripsiController.text,
|
||||
skemaId: selectedSkemaBantuanId.value!,
|
||||
lokasiPenyaluranId:
|
||||
selectedLokasiPenyaluranId.value!,
|
||||
jumlahPenerima: jumlahPenerima.value,
|
||||
tanggalPenyaluran: tanggalWaktuMulai,
|
||||
kategoriBantuanId: selectedSkemaBantuan
|
||||
.value!.kategoriBantuanId!,
|
||||
jumlahDiterimaPerOrang:
|
||||
jumlahDiterimaPerOrang.value,
|
||||
stokBantuanId: selectedSkemaBantuan
|
||||
.value!.stokBantuanId!,
|
||||
totalStokDibutuhkan:
|
||||
totalStokDibutuhkan.value);
|
||||
|
||||
//get back and refresh page
|
||||
Get.back();
|
||||
controller.refreshData();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
disabledBackgroundColor: Colors.grey.shade300,
|
||||
disabledForegroundColor: Colors.grey.shade600,
|
||||
),
|
||||
child: const Text(
|
||||
'Simpan Penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
Reference in New Issue
Block a user