diff --git a/lib/app/modules/petugas_desa/bindings/riwayat_pengaduan_binding.dart b/lib/app/modules/petugas_desa/bindings/riwayat_pengaduan_binding.dart new file mode 100644 index 0000000..d008652 --- /dev/null +++ b/lib/app/modules/petugas_desa/bindings/riwayat_pengaduan_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/riwayat_pengaduan_controller.dart'; + +class RiwayatPengaduanBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => RiwayatPengaduanController(), + ); + } +} diff --git a/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart b/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart index 3ba9dbc..b720f8d 100644 --- a/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart @@ -38,7 +38,6 @@ class DetailPenyaluranController extends GetxController { checkUserRole(); } else { isLoading.value = false; - print('DetailPenyaluranController - ID Penyaluran tidak ditemukan'); } } @@ -64,8 +63,6 @@ class DetailPenyaluranController extends GetxController { Future loadPenyaluranData(String penyaluranId) async { try { isLoading.value = true; - print( - 'DetailPenyaluranController - Memuat data penyaluran dengan ID: $penyaluranId'); // Ambil data penyaluran final penyaluranData = await _supabaseService.client @@ -74,8 +71,6 @@ class DetailPenyaluranController extends GetxController { .eq('id', penyaluranId) .single(); - print('DetailPenyaluranController - Data penyaluran: $penyaluranData'); - // Pastikan data yang diterima sesuai dengan tipe data yang diharapkan Map sanitizedData = Map.from(penyaluranData); @@ -87,42 +82,33 @@ class DetailPenyaluranController extends GetxController { } penyaluran.value = PenyaluranBantuanModel.fromJson(sanitizedData); - print( - 'DetailPenyaluranController - Model penyaluran: ${penyaluran.value?.nama}'); // Ambil data skema bantuan jika ada if (penyaluran.value?.skemaId != null && penyaluran.value!.skemaId!.isNotEmpty) { - print( - 'DetailPenyaluranController - Memuat skema bantuan dengan ID: ${penyaluran.value!.skemaId}'); final skemaData = await _supabaseService.client .from('xx02_skema_bantuan') .select('*') .eq('id', penyaluran.value!.skemaId!) .single(); - print('DetailPenyaluranController - Data skema bantuan: $skemaData'); - if (skemaData != null) { - // Pastikan data skema sesuai dengan tipe data yang diharapkan - Map sanitizedSkemaData = - Map.from(skemaData); + // Pastikan data skema sesuai dengan tipe data yang diharapkan + Map sanitizedSkemaData = + Map.from(skemaData); - // Konversi kuota ke int jika bertipe String - if (sanitizedSkemaData['kuota'] is String) { - sanitizedSkemaData['kuota'] = - int.tryParse(sanitizedSkemaData['kuota'] as String) ?? 0; - } - - // Konversi petugas_verifikasi_id ke int jika bertipe String - if (sanitizedSkemaData['petugas_verifikasi_id'] is String) { - sanitizedSkemaData['petugas_verifikasi_id'] = int.tryParse( - sanitizedSkemaData['petugas_verifikasi_id'] as String); - } - - skemaBantuan.value = SkemaBantuanModel.fromJson(sanitizedSkemaData); - print( - 'DetailPenyaluranController - Model skema bantuan: ${skemaBantuan.value?.nama}'); + // Konversi kuota ke int jika bertipe String + if (sanitizedSkemaData['kuota'] is String) { + sanitizedSkemaData['kuota'] = + int.tryParse(sanitizedSkemaData['kuota'] as String) ?? 0; } + + // Konversi petugas_verifikasi_id ke int jika bertipe String + if (sanitizedSkemaData['petugas_verifikasi_id'] is String) { + sanitizedSkemaData['petugas_verifikasi_id'] = int.tryParse( + sanitizedSkemaData['petugas_verifikasi_id'] as String); + } + + skemaBantuan.value = SkemaBantuanModel.fromJson(sanitizedSkemaData); } // Ambil data penerima penyaluran @@ -131,32 +117,22 @@ class DetailPenyaluranController extends GetxController { .select('*, warga:warga_id(*)') .eq('penyaluran_bantuan_id', penyaluranId); - print( - 'DetailPenyaluranController - Data penerima penyaluran: $penerimaPenyaluranData'); - if (penerimaPenyaluranData != null) { - final List penerima = []; - for (var item in penerimaPenyaluranData) { - // Pastikan data penerima sesuai dengan tipe data yang diharapkan - Map sanitizedPenerimaData = - Map.from(item); + final List penerima = []; + for (var item in penerimaPenyaluranData) { + // Pastikan data penerima sesuai dengan tipe data yang diharapkan + Map sanitizedPenerimaData = + Map.from(item); - // Konversi jumlah_bantuan ke double jika bertipe String - if (sanitizedPenerimaData['jumlah_bantuan'] is String) { - sanitizedPenerimaData['jumlah_bantuan'] = double.tryParse( - sanitizedPenerimaData['jumlah_bantuan'] as String); - } - - penerima.add(PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData)); + // Konversi jumlah_bantuan ke double jika bertipe String + if (sanitizedPenerimaData['jumlah_bantuan'] is String) { + sanitizedPenerimaData['jumlah_bantuan'] = double.tryParse( + sanitizedPenerimaData['jumlah_bantuan'] as String); } - penerimaPenyaluran.assignAll(penerima); - print( - 'DetailPenyaluranController - Jumlah penerima: ${penerima.length}'); - //print id - print('DetailPenyaluranController - ID penerima: ${penerima[0].id}'); + penerima.add(PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData)); } + penerimaPenyaluran.assignAll(penerima); } catch (e) { - print('Error loading penyaluran data: $e'); Get.snackbar( 'Error', 'Terjadi kesalahan saat memuat data penyaluran', @@ -198,7 +174,6 @@ class DetailPenyaluranController extends GetxController { snackPosition: SnackPosition.BOTTOM, ); } catch (e) { - print('Error memulai penyaluran: $e'); Get.snackbar( 'Error', 'Terjadi kesalahan saat memulai penyaluran bantuan', @@ -237,10 +212,6 @@ class DetailPenyaluranController extends GetxController { 'tanda_tangan': tandaTangan, }; - print( - 'DetailPenyaluranController - Updating penerima with ID: ${penerima.id}'); - print('DetailPenyaluranController - Update data: $updateData'); - await _supabaseService.client .from('penerima_penyaluran') .update(updateData) @@ -251,8 +222,6 @@ class DetailPenyaluranController extends GetxController { // Tidak perlu menampilkan snackbar di sini karena sudah ditampilkan di halaman konfirmasi penerima } catch (e) { - print('Error konfirmasi penerimaan: $e'); - // Tidak perlu menampilkan snackbar di sini karena sudah ditampilkan di halaman konfirmasi penerima rethrow; // Melempar kembali exception agar dapat ditangkap di _konfirmasiPenerimaan } finally { isProcessing.value = false; @@ -314,7 +283,6 @@ class DetailPenyaluranController extends GetxController { snackPosition: SnackPosition.BOTTOM, ); } catch (e) { - print('Error menyelesaikan penyaluran: $e'); Get.snackbar( 'Error', 'Terjadi kesalahan saat menyelesaikan penyaluran bantuan', @@ -353,7 +321,6 @@ class DetailPenyaluranController extends GetxController { snackPosition: SnackPosition.BOTTOM, ); } catch (e) { - print('Error membatalkan penyaluran: $e'); Get.snackbar( 'Error', 'Terjadi kesalahan saat membatalkan penyaluran bantuan', @@ -378,21 +345,14 @@ class DetailPenyaluranController extends GetxController { '${filePrefix}_${DateTime.now().millisecondsSinceEpoch}.jpg'; final file = File(filePath); - print( - 'Uploading ${isTandaTangan ? "tanda tangan" : "bukti penerimaan"} dari: $filePath'); - print('File exists: ${file.existsSync()}'); - print('File size: ${file.lengthSync()} bytes'); - if (!file.existsSync()) { throw Exception('File tidak ditemukan: $filePath'); } - print('Uploading ke bucket: $folderName dengan nama file: $fileName'); final storageResponse = await _supabaseService.client.storage .from(folderName) .upload(fileName, file); - print('Storage response: $storageResponse'); if (storageResponse.isEmpty) { throw Exception( 'Gagal mengupload ${isTandaTangan ? 'tanda tangan' : 'bukti penerimaan'}'); @@ -402,7 +362,6 @@ class DetailPenyaluranController extends GetxController { .from(folderName) .getPublicUrl(fileName); - print('File URL: $fileUrl'); if (fileUrl.isEmpty) { throw Exception( 'Gagal mendapatkan URL ${isTandaTangan ? 'tanda tangan' : 'bukti penerimaan'}'); @@ -422,42 +381,33 @@ class DetailPenyaluranController extends GetxController { Future loadPenyaluranDetails(String penyaluranId) async { try { isLoading.value = true; - print( - 'DetailPenyaluranController - Memuat detail penyaluran dengan ID: $penyaluranId'); // Ambil data skema bantuan jika ada if (penyaluran.value?.skemaId != null && penyaluran.value!.skemaId!.isNotEmpty) { - print( - 'DetailPenyaluranController - Memuat skema bantuan dengan ID: ${penyaluran.value!.skemaId}'); final skemaData = await _supabaseService.client .from('xx02_skema_bantuan') .select('*') .eq('id', penyaluran.value!.skemaId!) .single(); - print('DetailPenyaluranController - Data skema bantuan: $skemaData'); - if (skemaData != null) { - // Pastikan data skema sesuai dengan tipe data yang diharapkan - Map sanitizedSkemaData = - Map.from(skemaData); + // Pastikan data skema sesuai dengan tipe data yang diharapkan + Map sanitizedSkemaData = + Map.from(skemaData); - // Konversi kuota ke int jika bertipe String - if (sanitizedSkemaData['kuota'] is String) { - sanitizedSkemaData['kuota'] = - int.tryParse(sanitizedSkemaData['kuota'] as String) ?? 0; - } - - // Konversi petugas_verifikasi_id ke int jika bertipe String - if (sanitizedSkemaData['petugas_verifikasi_id'] is String) { - sanitizedSkemaData['petugas_verifikasi_id'] = int.tryParse( - sanitizedSkemaData['petugas_verifikasi_id'] as String); - } - - skemaBantuan.value = SkemaBantuanModel.fromJson(sanitizedSkemaData); - print( - 'DetailPenyaluranController - Model skema bantuan: ${skemaBantuan.value?.nama}'); + // Konversi kuota ke int jika bertipe String + if (sanitizedSkemaData['kuota'] is String) { + sanitizedSkemaData['kuota'] = + int.tryParse(sanitizedSkemaData['kuota'] as String) ?? 0; } + + // Konversi petugas_verifikasi_id ke int jika bertipe String + if (sanitizedSkemaData['petugas_verifikasi_id'] is String) { + sanitizedSkemaData['petugas_verifikasi_id'] = int.tryParse( + sanitizedSkemaData['petugas_verifikasi_id'] as String); + } + + skemaBantuan.value = SkemaBantuanModel.fromJson(sanitizedSkemaData); } // Ambil data penerima penyaluran @@ -466,33 +416,26 @@ class DetailPenyaluranController extends GetxController { .select('*, warga:warga_id(*)') .eq('penyaluran_bantuan_id', penyaluranId); - print( - 'DetailPenyaluranController - Data penerima penyaluran: $penerimaPenyaluranData'); - if (penerimaPenyaluranData != null) { - final List penerima = []; - for (var item in penerimaPenyaluranData) { - // Pastikan data penerima sesuai dengan tipe data yang diharapkan - Map sanitizedPenerimaData = - Map.from(item); + final List penerima = []; + for (var item in penerimaPenyaluranData) { + // Pastikan data penerima sesuai dengan tipe data yang diharapkan + Map sanitizedPenerimaData = + Map.from(item); - // Konversi jumlah_bantuan ke double jika bertipe String - if (sanitizedPenerimaData['jumlah_bantuan'] is String) { - sanitizedPenerimaData['jumlah_bantuan'] = double.tryParse( - sanitizedPenerimaData['jumlah_bantuan'] as String); - } - - penerima.add(PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData)); + // Konversi jumlah_bantuan ke double jika bertipe String + if (sanitizedPenerimaData['jumlah_bantuan'] is String) { + sanitizedPenerimaData['jumlah_bantuan'] = double.tryParse( + sanitizedPenerimaData['jumlah_bantuan'] as String); } - penerimaPenyaluran.assignAll(penerima); - print( - 'DetailPenyaluranController - Jumlah penerima: ${penerima.length}'); - if (penerima.isNotEmpty) { - print('DetailPenyaluranController - ID penerima: ${penerima[0].id}'); - } + penerima.add(PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData)); } + penerimaPenyaluran.assignAll(penerima); + + // if (penerima.isNotEmpty) { + // print('DetailPenyaluranController - ID penerima: ${penerima[0].id}'); + // } } catch (e) { - print('Error loading penyaluran details: $e'); Get.snackbar( 'Error', 'Terjadi kesalahan saat memuat detail penyaluran', diff --git a/lib/app/modules/petugas_desa/controllers/pengaduan_controller.dart b/lib/app/modules/petugas_desa/controllers/pengaduan_controller.dart index 8dd19be..596fdc3 100644 --- a/lib/app/modules/petugas_desa/controllers/pengaduan_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/pengaduan_controller.dart @@ -15,7 +15,7 @@ class PengaduanController extends GetxController { final RxBool isUploading = false.obs; // Indeks kategori yang dipilih untuk filter - final RxInt selectedCategoryIndex = 0.obs; + final RxInt selectedCategoryIndex = 4.obs; // Data untuk pengaduan final RxList daftarPengaduan = [].obs; @@ -346,6 +346,10 @@ class PengaduanController extends GetxController { return daftarPengaduan .where((item) => item.status == 'SELESAI') .toList(); + case 4: + return daftarPengaduan + .where((item) => item.status != 'SELESAI') + .toList(); default: return daftarPengaduan; } diff --git a/lib/app/modules/petugas_desa/controllers/riwayat_pengaduan_controller.dart b/lib/app/modules/petugas_desa/controllers/riwayat_pengaduan_controller.dart new file mode 100644 index 0000000..34bf314 --- /dev/null +++ b/lib/app/modules/petugas_desa/controllers/riwayat_pengaduan_controller.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/data/models/pengaduan_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/services/supabase_service.dart'; + +class RiwayatPengaduanController extends GetxController { + final AuthController _authController = Get.find(); + final SupabaseService _supabaseService = SupabaseService.to; + + final RxBool isLoading = false.obs; + + // Data untuk pengaduan + final RxList daftarRiwayatPengaduan = [].obs; + + // Controller untuk pencarian + final TextEditingController searchController = TextEditingController(); + + UserModel? get user => _authController.user; + + @override + void onInit() { + super.onInit(); + loadRiwayatPengaduanData(); + } + + @override + void onClose() { + searchController.dispose(); + super.onClose(); + } + + Future loadRiwayatPengaduanData() async { + isLoading.value = true; + try { + final pengaduanData = + await _supabaseService.getPengaduanWithPenerimaPenyaluran(); + if (pengaduanData != null) { + // Filter hanya pengaduan dengan status SELESAI + final List selesaiPengaduan = pengaduanData + .map((data) => PengaduanModel.fromJson(data)) + .where((item) => item.status == 'SELESAI') + .toList(); + + daftarRiwayatPengaduan.value = selesaiPengaduan; + } + } catch (e) { + print('Error loading riwayat pengaduan data: $e'); + } finally { + isLoading.value = false; + } + } + + Future refreshData() async { + isLoading.value = true; + try { + await loadRiwayatPengaduanData(); + } finally { + isLoading.value = false; + } + } + + List getFilteredRiwayatPengaduan() { + if (searchController.text.isEmpty) { + return daftarRiwayatPengaduan; + } + + final searchQuery = searchController.text.toLowerCase(); + return daftarRiwayatPengaduan.where((item) { + final namaWarga = item.warga?['nama']?.toString().toLowerCase() ?? ''; + final nik = item.warga?['nik']?.toString().toLowerCase() ?? ''; + final deskripsi = item.deskripsi?.toLowerCase() ?? ''; + + return namaWarga.contains(searchQuery) || + nik.contains(searchQuery) || + deskripsi.contains(searchQuery); + }).toList(); + } + + Future> getDetailPengaduan(String pengaduanId) async { + try { + // Ambil data pengaduan + final pengaduanData = + await _supabaseService.client.from('pengaduan').select(''' + *, + penerima_penyaluran:penerima_penyaluran_id( + *, + penyaluran_bantuan:penyaluran_bantuan_id(*), + stok_bantuan:stok_bantuan_id(*), + warga:warga_id(*) + ), + warga:warga_id(*) + ''').eq('id', pengaduanId).single(); + + // Ambil data tindakan pengaduan + final tindakanData = + await _supabaseService.getTindakanPengaduan(pengaduanId); + + // Gabungkan data + final result = { + 'pengaduan': pengaduanData, + 'tindakan': tindakanData ?? [], + }; + + return result; + } catch (e) { + print('Error getting detail pengaduan: $e'); + return { + 'pengaduan': null, + 'tindakan': [], + }; + } + } +} diff --git a/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart b/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart index 6e275f9..c2d1ac0 100644 --- a/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart +++ b/lib/app/modules/petugas_desa/views/detail_pengaduan_view.dart @@ -11,7 +11,6 @@ import 'package:penyaluran_app/app/widgets/section_header.dart'; import 'package:penyaluran_app/app/services/supabase_service.dart'; import 'package:timeline_tile/timeline_tile.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; import 'dart:io'; import 'package:penyaluran_app/app/widgets/inputs/dropdown_input.dart'; import 'package:penyaluran_app/app/widgets/inputs/text_input.dart'; diff --git a/lib/app/modules/petugas_desa/views/pengaduan_view.dart b/lib/app/modules/petugas_desa/views/pengaduan_view.dart index 2d68969..fd65882 100644 --- a/lib/app/modules/petugas_desa/views/pengaduan_view.dart +++ b/lib/app/modules/petugas_desa/views/pengaduan_view.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/pengaduan_controller.dart'; import 'package:penyaluran_app/app/theme/app_theme.dart'; import 'package:penyaluran_app/app/utils/date_time_helper.dart'; +import 'package:penyaluran_app/app/routes/app_pages.dart'; class PengaduanView extends GetView { const PengaduanView({super.key}); @@ -67,57 +68,61 @@ class PengaduanView extends GetView { Widget _buildPengaduanSummary(BuildContext context) { return Obx(() { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - gradient: AppTheme.primaryGradient, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Ringkasan Pengaduan', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - color: Colors.white, - ), + return Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: AppTheme.primaryGradient, + borderRadius: BorderRadius.circular(12), ), - const SizedBox(height: 16), - Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: _buildSummaryItem( - context, - icon: Icons.pending_actions, - title: 'Diproses', - value: controller.jumlahDiproses.toString(), - color: Colors.orange, - ), + Text( + 'Ringkasan Pengaduan', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), - Expanded( - child: _buildSummaryItem( - context, - icon: Icons.engineering, - title: 'Tindakan', - value: controller.jumlahTindakan.toString(), - color: Colors.blue, - ), - ), - Expanded( - child: _buildSummaryItem( - context, - icon: Icons.check_circle, - title: 'Selesai', - value: controller.jumlahSelesai.toString(), - color: Colors.green, - ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: _buildSummaryItem( + context, + icon: Icons.pending_actions, + title: 'Diproses', + value: controller.jumlahDiproses.toString(), + color: Colors.orange, + ), + ), + Expanded( + child: _buildSummaryItem( + context, + icon: Icons.engineering, + title: 'Tindakan', + value: controller.jumlahTindakan.toString(), + color: Colors.blue, + ), + ), + Expanded( + child: _buildSummaryItem( + context, + icon: Icons.check_circle, + title: 'Selesai', + value: controller.jumlahSelesai.toString(), + color: Colors.green, + ), + ), + ], ), ], ), - ], - ), + ), + ], ); }); } @@ -215,6 +220,10 @@ class PengaduanView extends GetView { value: 3, child: Text('Selesai'), ), + const PopupMenuItem( + value: 4, + child: Text('Semua Kecuali Selesai'), + ), ], ), ), diff --git a/lib/app/modules/petugas_desa/views/petugas_desa_view.dart b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart index b6f55f3..942d4ca 100644 --- a/lib/app/modules/petugas_desa/views/petugas_desa_view.dart +++ b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart @@ -125,6 +125,21 @@ class PetugasDesaView extends GetView { ); } + // if 3 + if (activeTab == 3) { + return Row( + children: [ + IconButton( + onPressed: () { + Get.toNamed('/petugas-desa/riwayat-pengaduan'); + }, + icon: const Icon(Icons.history), + tooltip: 'Riwayat Pengaduan', + ), + notificationButton, + ], + ); + } return notificationButton; }), ], diff --git a/lib/app/modules/petugas_desa/views/riwayat_pengaduan_view.dart b/lib/app/modules/petugas_desa/views/riwayat_pengaduan_view.dart new file mode 100644 index 0000000..d4a41df --- /dev/null +++ b/lib/app/modules/petugas_desa/views/riwayat_pengaduan_view.dart @@ -0,0 +1,411 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/riwayat_pengaduan_controller.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; +import 'package:penyaluran_app/app/utils/date_time_helper.dart'; + +class RiwayatPengaduanView extends GetView { + const RiwayatPengaduanView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Riwayat Pengaduan'), + ), + body: RefreshIndicator( + onRefresh: controller.refreshData, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Pencarian + _buildSearch(context), + + // Informasi terakhir update + _buildLastUpdateInfo(context), + + const SizedBox(height: 20), + + // Daftar riwayat pengaduan + _buildRiwayatPengaduanList(context), + ], + ), + ), + ), + ), + ); + } + + // Tambahkan widget untuk menampilkan waktu terakhir update + Widget _buildLastUpdateInfo(BuildContext context) { + final lastUpdate = DateTime.now(); + final formattedDate = DateTimeHelper.formatDateTimeWithHour(lastUpdate); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + children: [ + Icon(Icons.update, size: 16, color: Colors.grey[600]), + const SizedBox(width: 4), + Text( + 'Data terupdate: $formattedDate', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ); + } + + Widget _buildSearch(BuildContext context) { + return TextField( + controller: controller.searchController, + decoration: InputDecoration( + hintText: 'Cari riwayat pengaduan...', + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + filled: true, + fillColor: Colors.grey.shade100, + contentPadding: const EdgeInsets.symmetric(vertical: 0), + ), + onChanged: (value) { + // Implementasi pencarian + controller.refreshData(); + }, + ); + } + + Widget _buildRiwayatPengaduanList(BuildContext context) { + return Obx(() { + if (controller.isLoading.value) { + return const Center( + child: Padding( + padding: EdgeInsets.all(20.0), + child: CircularProgressIndicator(), + ), + ); + } + + final filteredPengaduan = controller.getFilteredRiwayatPengaduan(); + + if (filteredPengaduan.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + children: [ + Icon( + Icons.inbox_outlined, + size: 80, + color: Colors.grey.shade400, + ), + const SizedBox(height: 16), + Text( + 'Belum ada riwayat pengaduan', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Daftar Riwayat Pengaduan', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Text( + '${DateTimeHelper.formatNumber(filteredPengaduan.length)} item', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.grey, + ), + ), + ], + ), + const SizedBox(height: 12), + ...filteredPengaduan + .map((item) => _buildPengaduanItem(context, item)), + ], + ); + }); + } + + Widget _buildPengaduanItem(BuildContext context, dynamic item) { + // Format tanggal menggunakan DateTimeHelper + String formattedDate = ''; + if (item.tanggalPengaduan != null) { + formattedDate = DateTimeHelper.formatDate(item.tanggalPengaduan); + } else if (item.createdAt != null) { + formattedDate = DateTimeHelper.formatDate(item.createdAt); + } + + return InkWell( + onTap: () { + // Navigasi ke halaman detail pengaduan + Get.toNamed('/detail-pengaduan', arguments: {'id': item.id}); + }, + borderRadius: BorderRadius.circular(12), + child: Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 12), + 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: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + 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, + ), + ), + ), + const SizedBox(width: 12), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.successColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.check_circle, + size: 16, + color: AppTheme.successColor, + ), + const SizedBox(width: 4), + Text( + 'SELESAI', + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppTheme.successColor, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + 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: 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', + value: formattedDate, + ), + ), + ], + ), + ], + if (item.ratingWarga != null && item.ratingWarga > 0) ...[ + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.amber.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.amber.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Feedback Warga', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Colors.amber, + ), + ), + Row( + children: List.generate(5, (index) { + return Icon( + index < (item.ratingWarga ?? 0) + ? Icons.star + : Icons.star_border, + color: Colors.amber, + size: 16, + ); + }), + ), + ], + ), + if (item.feedbackWarga != null && + item.feedbackWarga.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + '${item.feedbackWarga}', + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ], + ), + ), + ], + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + 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), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + 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, + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index a05ecea..c810745 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -13,6 +13,8 @@ import 'package:penyaluran_app/app/modules/petugas_desa/views/tambah_penyaluran_ import 'package:penyaluran_app/app/modules/petugas_desa/views/riwayat_penyaluran_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_penyaluran_page.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penyaluran_binding.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/views/riwayat_pengaduan_view.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/bindings/riwayat_pengaduan_binding.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penerima_binding.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/donatur_binding.dart'; @@ -142,5 +144,10 @@ class AppPages { page: () => const WargaDetailPengaduanView(), binding: WargaBinding(), ), + GetPage( + name: _Paths.riwayatPengaduan, + page: () => const RiwayatPengaduanView(), + binding: RiwayatPengaduanBinding(), + ), ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index e71ee22..759fc64 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -30,6 +30,7 @@ abstract class Routes { static const wargaDetailPenerimaan = _Paths.wargaDetailPenerimaan; static const detailPengaduan = _Paths.detailPengaduan; static const wargaDetailPengaduan = _Paths.wargaDetailPengaduan; + static const riwayatPengaduan = _Paths.riwayatPengaduan; } abstract class _Paths { @@ -62,4 +63,5 @@ abstract class _Paths { static const wargaDetailPenerimaan = '/warga/detail-penerimaan'; static const detailPengaduan = '/detail-pengaduan'; static const wargaDetailPengaduan = '/warga/detail-pengaduan'; + static const riwayatPengaduan = '/petugas-desa/riwayat-pengaduan'; }