From 7ee56903eec04358b5d35c0714080bdd6fb23693 Mon Sep 17 00:00:00 2001 From: Khafidh Fuadi Date: Mon, 17 Mar 2025 21:18:32 +0700 Subject: [PATCH] Hapus pernyataan print yang tidak diperlukan di DetailPenyaluranController untuk meningkatkan kebersihan kode. Perbarui filter kategori di PengaduanController untuk menyertakan opsi baru "Semua Kecuali Selesai". Modifikasi tampilan di PengaduanView untuk menampilkan ringkasan pengaduan dengan lebih baik dan tambahkan rute baru untuk RiwayatPengaduan. --- .../bindings/riwayat_pengaduan_binding.dart | 11 + .../detail_penyaluran_controller.dart | 169 +++---- .../controllers/pengaduan_controller.dart | 6 +- .../riwayat_pengaduan_controller.dart | 115 +++++ .../views/detail_pengaduan_view.dart | 1 - .../petugas_desa/views/pengaduan_view.dart | 99 +++-- .../petugas_desa/views/petugas_desa_view.dart | 15 + .../views/riwayat_pengaduan_view.dart | 411 ++++++++++++++++++ lib/app/routes/app_pages.dart | 7 + lib/app/routes/app_routes.dart | 2 + 10 files changed, 676 insertions(+), 160 deletions(-) create mode 100644 lib/app/modules/petugas_desa/bindings/riwayat_pengaduan_binding.dart create mode 100644 lib/app/modules/petugas_desa/controllers/riwayat_pengaduan_controller.dart create mode 100644 lib/app/modules/petugas_desa/views/riwayat_pengaduan_view.dart 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'; }