diff --git a/lib/app/modules/petugas_desa/bindings/petugas_desa_binding.dart b/lib/app/modules/petugas_desa/bindings/petugas_desa_binding.dart index 18a78fb..4f4950e 100644 --- a/lib/app/modules/petugas_desa/bindings/petugas_desa_binding.dart +++ b/lib/app/modules/petugas_desa/bindings/petugas_desa_binding.dart @@ -6,6 +6,7 @@ class PetugasDesaBinding extends Bindings { void dependencies() { Get.lazyPut( () => PetugasDesaController(), + fenix: true, ); } } diff --git a/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart new file mode 100644 index 0000000..f27643e --- /dev/null +++ b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; + +class JadwalSectionWidget extends StatelessWidget { + final PetugasDesaController controller; + final String title; + final List> jadwalList; + final String status; + + const JadwalSectionWidget({ + Key? key, + required this.controller, + required this.title, + required this.jadwalList, + required this.status, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Obx(() { + final currentJadwalList = _getCurrentJadwalList(); + + if (currentJadwalList.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.withAlpha(20), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + 'Tidak ada jadwal $title', + style: textTheme.titleMedium?.copyWith( + color: Colors.grey.shade600, + ), + ), + ), + ); + } + + return Column( + children: currentJadwalList + .map((jadwal) => _buildJadwalItem(textTheme, jadwal)) + .toList(), + ); + }), + ], + ); + } + + List> _getCurrentJadwalList() { + switch (title) { + case 'Hari Ini': + return controller.jadwalHariIni; + case 'Mendatang': + return controller.jadwalMendatang; + case 'Selesai': + return controller.jadwalSelesai; + default: + return jadwalList; + } + } + + Widget _buildJadwalItem(TextTheme textTheme, Map jadwal) { + Color statusColor; + switch (status) { + case 'Aktif': + statusColor = Colors.green; + break; + case 'Terjadwal': + statusColor = Colors.blue; + break; + case 'Selesai': + statusColor = Colors.grey; + break; + default: + statusColor = Colors.orange; + } + + return Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 10), + 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( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + jadwal['lokasi'] ?? '', + style: textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: statusColor.withAlpha(26), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + status, + style: textTheme.bodySmall?.copyWith( + color: statusColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + 'Jenis Bantuan: ${jadwal['jenis_bantuan'] ?? ''}', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 4), + Text( + 'Tanggal: ${jadwal['tanggal'] ?? ''}', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 4), + Text( + 'Waktu: ${jadwal['waktu'] ?? ''}', + style: textTheme.bodyMedium, + ), + if (jadwal['jumlah_penerima'] != null) ...[ + const SizedBox(height: 4), + Text( + 'Jumlah Penerima: ${jadwal['jumlah_penerima']}', + style: textTheme.bodyMedium, + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart b/lib/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart new file mode 100644 index 0000000..79d6f6c --- /dev/null +++ b/lib/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; +import 'package:penyaluran_app/app/routes/app_pages.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class PermintaanPenjadwalanSummaryWidget extends StatelessWidget { + final PetugasDesaController controller; + + const PermintaanPenjadwalanSummaryWidget({ + Key? key, + required this.controller, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return Obx(() { + final jumlahPermintaan = controller.jumlahPermintaanPenjadwalan.value; + final permintaanList = controller.permintaanPenjadwalan; + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withAlpha(30), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + border: Border.all( + color: jumlahPermintaan > 0 + ? Colors.orange.withAlpha(50) + : Colors.grey.withAlpha(30), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Permintaan Penjadwalan', + style: textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: jumlahPermintaan > 0 + ? Colors.red.withAlpha(26) + : Colors.grey.withAlpha(26), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '$jumlahPermintaan', + style: textTheme.bodySmall?.copyWith( + color: jumlahPermintaan > 0 ? Colors.red : Colors.grey, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + if (jumlahPermintaan == 0) + Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Column( + children: [ + Icon( + Icons.event_note, + size: 48, + color: Colors.grey.shade400, + ), + const SizedBox(height: 8), + Text( + 'Tidak ada permintaan penjadwalan', + style: textTheme.bodyMedium?.copyWith( + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + ) + else + Column( + children: [ + ...permintaanList.take(1).map((permintaan) => + _buildPermintaanPreview(textTheme, permintaan)), + if (jumlahPermintaan > 1) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + '+ ${jumlahPermintaan - 1} permintaan lainnya', + style: textTheme.bodySmall?.copyWith( + color: Colors.grey.shade600, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () => Get.toNamed(Routes.permintaanPenjadwalan), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + icon: const Icon(Icons.visibility), + label: const Text('Lihat Semua Permintaan'), + ), + ), + ], + ), + ); + }); + } + + Widget _buildPermintaanPreview( + TextTheme textTheme, Map permintaan) { + return Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey.withAlpha(15), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + permintaan['nama'] ?? '', + style: textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.orange.withAlpha(26), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Menunggu', + style: textTheme.bodySmall?.copyWith( + color: Colors.orange, + fontWeight: FontWeight.bold, + fontSize: 10, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + 'Jenis: ${permintaan['jenis_bantuan'] ?? ''}', + style: textTheme.bodySmall, + overflow: TextOverflow.ellipsis, + ), + Text( + 'Tanggal: ${permintaan['tanggal_permintaan'] ?? ''}', + style: textTheme.bodySmall, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/components/permintaan_penjadwalan_widget.dart b/lib/app/modules/petugas_desa/components/permintaan_penjadwalan_widget.dart new file mode 100644 index 0000000..82d7297 --- /dev/null +++ b/lib/app/modules/petugas_desa/components/permintaan_penjadwalan_widget.dart @@ -0,0 +1,348 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class PermintaanPenjadwalanWidget extends StatelessWidget { + final PetugasDesaController controller; + + const PermintaanPenjadwalanWidget({ + Key? key, + required this.controller, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Permintaan Penjadwalan', + style: textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Obx(() => Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.red.withAlpha(26), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${controller.jumlahPermintaanPenjadwalan.value}', + style: textTheme.bodySmall?.copyWith( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + )), + ], + ), + const SizedBox(height: 10), + Obx(() { + final permintaanList = controller.permintaanPenjadwalan; + + // Jika tidak ada permintaan, tampilkan pesan kosong + if (permintaanList.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.withAlpha(20), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Column( + children: [ + Icon( + Icons.event_note, + size: 48, + color: Colors.grey.shade400, + ), + const SizedBox(height: 16), + Text( + 'Tidak ada permintaan penjadwalan', + style: textTheme.titleMedium?.copyWith( + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + ); + } + + return Column( + children: permintaanList + .map( + (permintaan) => _buildPermintaanItem(textTheme, permintaan)) + .toList(), + ); + }), + ], + ); + } + + // Widget untuk menampilkan item permintaan penjadwalan + Widget _buildPermintaanItem( + TextTheme textTheme, Map permintaan) { + return Container( + width: double.infinity, + margin: const EdgeInsets.only(bottom: 10), + 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), + ), + ], + border: Border.all( + color: Colors.orange.withAlpha(50), + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + permintaan['nama'] ?? '', + style: textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.orange.withAlpha(26), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Menunggu', + style: textTheme.bodySmall?.copyWith( + color: Colors.orange, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + 'NIK: ${permintaan['nik'] ?? ''}', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 4), + Text( + 'Jenis Bantuan: ${permintaan['jenis_bantuan'] ?? ''}', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 4), + Text( + 'Tanggal Permintaan: ${permintaan['tanggal_permintaan'] ?? ''}', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 4), + Text( + 'Alamat: ${permintaan['alamat'] ?? ''}', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton( + onPressed: () => _showTolakDialog(permintaan), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.red, + side: const BorderSide(color: Colors.red), + ), + child: const Text('Tolak'), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: () => _showKonfirmasiDialog(permintaan), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + ), + child: const Text('Konfirmasi'), + ), + ], + ), + ], + ), + ), + ); + } + + // Dialog untuk konfirmasi permintaan + void _showKonfirmasiDialog(Map permintaan) { + String? selectedJadwalId; + + // Data jadwal yang tersedia dari controller + final jadwalOptions = controller.jadwalMendatang.map((jadwal) { + return DropdownMenuItem( + value: jadwal['id'], + child: Text( + '${jadwal['tanggal']} - ${jadwal['lokasi']} (${jadwal['jenis_bantuan']})'), + ); + }).toList(); + + // Tambahkan opsi jadwal lain jika diperlukan + jadwalOptions.add( + const DropdownMenuItem( + value: '3', + child: Text('25 April 2023 - Kantor Kepala Desa (Beras)'), + ), + ); + + Get.dialog( + AlertDialog( + title: const Text('Konfirmasi Permintaan'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan['nama']}.'), + const SizedBox(height: 16), + const Text('Pilih jadwal penyaluran:'), + const SizedBox(height: 8), + DropdownButtonFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: + EdgeInsets.symmetric(horizontal: 12, vertical: 8), + ), + items: jadwalOptions, + onChanged: (value) { + selectedJadwalId = value; + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () { + if (selectedJadwalId != null) { + // Panggil metode konfirmasi di controller + controller.konfirmasiPermintaanPenjadwalan( + permintaan['id'], + selectedJadwalId!, + ); + + Get.back(); + Get.snackbar( + 'Berhasil', + 'Permintaan penjadwalan berhasil dikonfirmasi', + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } else { + Get.snackbar( + 'Peringatan', + 'Silakan pilih jadwal penyaluran terlebih dahulu', + backgroundColor: Colors.orange, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + ), + child: const Text('Konfirmasi'), + ), + ], + ), + ); + } + + // Dialog untuk menolak permintaan + void _showTolakDialog(Map permintaan) { + final TextEditingController alasanController = TextEditingController(); + + Get.dialog( + AlertDialog( + title: const Text('Tolak Permintaan'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Anda akan menolak permintaan penjadwalan dari ${permintaan['nama']}.'), + const SizedBox(height: 16), + const Text('Alasan penolakan:'), + const SizedBox(height: 8), + TextField( + controller: alasanController, + maxLines: 3, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Masukkan alasan penolakan', + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () { + if (alasanController.text.trim().isNotEmpty) { + // Panggil metode tolak di controller + controller.tolakPermintaanPenjadwalan( + permintaan['id'], + alasanController.text.trim(), + ); + + Get.back(); + Get.snackbar( + 'Berhasil', + 'Permintaan penjadwalan berhasil ditolak', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } else { + Get.snackbar( + 'Peringatan', + 'Silakan masukkan alasan penolakan', + backgroundColor: Colors.orange, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + child: const Text('Tolak'), + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart b/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart index 49c3fcd..f9a2950 100644 --- a/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart @@ -31,6 +31,11 @@ class PetugasDesaController extends GetxController { final RxList> jadwalSelesai = >[].obs; + // Data untuk permintaan penjadwalan + final RxList> permintaanPenjadwalan = + >[].obs; + final RxInt jumlahPermintaanPenjadwalan = 0.obs; + // Data untuk notifikasi final RxList> notifikasiBelumDibaca = >[].obs; @@ -50,6 +55,13 @@ class PetugasDesaController extends GetxController { final RxInt jumlahTerverifikasi = 0.obs; final RxInt jumlahDitolak = 0.obs; + // Data untuk pengaduan + final RxList> daftarPengaduan = + >[].obs; + final RxInt jumlahDiproses = 0.obs; + final RxInt jumlahTindakan = 0.obs; + final RxInt jumlahSelesai = 0.obs; + // Controller untuk pencarian final TextEditingController searchController = TextEditingController(); @@ -60,12 +72,19 @@ class PetugasDesaController extends GetxController { @override void onInit() { super.onInit(); + + // Inisialisasi manual untuk pengaduan (untuk debugging) + jumlahDiproses.value = 3; + print('onInit - Jumlah pengaduan diproses: ${jumlahDiproses.value}'); + loadRoleData(); loadDashboardData(); loadJadwalData(); + loadPermintaanPenjadwalanData(); loadNotifikasiData(); loadInventarisData(); loadPenitipanData(); + loadPengaduanData(); } @override @@ -187,6 +206,43 @@ class PetugasDesaController extends GetxController { } } + Future loadPermintaanPenjadwalanData() async { + try { + // Simulasi data untuk permintaan penjadwalan + await Future.delayed(const Duration(milliseconds: 600)); + + permintaanPenjadwalan.value = [ + { + 'id': '1', + 'nama': 'Ahmad Sulaiman', + 'nik': '3201234567890001', + 'jenis_bantuan': 'Beras', + 'tanggal_permintaan': '14 April 2023', + 'alamat': 'Dusun Sukamaju RT 02/03', + 'status': 'menunggu', + }, + { + 'id': '2', + 'nama': 'Siti Aminah', + 'nik': '3201234567890002', + 'jenis_bantuan': 'Sembako', + 'tanggal_permintaan': '13 April 2023', + 'alamat': 'Dusun Sukamaju RT 01/03', + 'status': 'menunggu', + }, + ]; + + jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length; + + // Di implementasi nyata, data akan diambil dari Supabase + // final result = await _supabaseService.getPermintaanPenjadwalanData(); + // permintaanPenjadwalan.value = result ?? []; + // jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length; + } catch (e) { + print('Error loading permintaan penjadwalan data: $e'); + } + } + Future loadNotifikasiData() async { try { // Simulasi data untuk notifikasi @@ -348,6 +404,126 @@ class PetugasDesaController extends GetxController { } } + Future loadPengaduanData() async { + try { + // Simulasi data untuk pengaduan + await Future.delayed(const Duration(milliseconds: 650)); + + // Pastikan data pengaduan tidak kosong + daftarPengaduan.value = [ + { + 'id': '1', + 'nama': 'Budi Santoso', + 'nik': '3201020107030011', + 'jenis_pengaduan': 'Bantuan Tidak Diterima', + 'deskripsi': + 'Saya belum menerima bantuan beras yang dijadwalkan minggu lalu', + 'tanggal': '15 April 2023', + 'status': 'Diproses', + }, + { + 'id': '2', + 'nama': 'Siti Rahayu', + 'nik': '3201020107030010', + 'jenis_pengaduan': 'Kualitas Bantuan', + 'deskripsi': + 'Beras yang diterima berkualitas buruk dan tidak layak konsumsi', + 'tanggal': '14 April 2023', + 'status': 'Tindakan', + 'tindakan': + 'Pengecekan kualitas beras di gudang dan pengambilan sampel', + }, + { + 'id': '3', + 'nama': 'Ahmad Fauzi', + 'nik': '3201020107030013', + 'jenis_pengaduan': 'Jumlah Bantuan', + 'deskripsi': + 'Jumlah bantuan yang diterima tidak sesuai dengan yang dijanjikan', + 'tanggal': '13 April 2023', + 'status': 'Tindakan', + 'tindakan': + 'Verifikasi data penerima dan jumlah bantuan yang seharusnya diterima', + }, + { + 'id': '4', + 'nama': 'Dewi Lestari', + 'nik': '3201020107030012', + 'jenis_pengaduan': 'Jadwal Penyaluran', + 'deskripsi': + 'Jadwal penyaluran bantuan sering berubah tanpa pemberitahuan', + 'tanggal': '10 April 2023', + 'status': 'Selesai', + 'tindakan': + 'Koordinasi dengan tim penyaluran untuk perbaikan sistem pemberitahuan', + 'hasil': + 'Implementasi sistem notifikasi perubahan jadwal melalui SMS dan pengumuman di balai desa', + }, + // Tambahkan data pengaduan dengan status 'Diproses' untuk memastikan counter muncul + { + 'id': '5', + 'nama': 'Joko Widodo', + 'nik': '3201020107030014', + 'jenis_pengaduan': 'Bantuan Tidak Sesuai', + 'deskripsi': + 'Bantuan yang diterima tidak sesuai dengan yang dijanjikan', + 'tanggal': '16 April 2023', + 'status': 'Diproses', + }, + { + 'id': '6', + 'nama': 'Anita Sari', + 'nik': '3201020107030015', + 'jenis_pengaduan': 'Bantuan Tidak Tepat Sasaran', + 'deskripsi': + 'Bantuan diberikan kepada warga yang tidak berhak menerima', + 'tanggal': '17 April 2023', + 'status': 'Diproses', + }, + ]; + + // Hitung jumlah pengaduan berdasarkan status + int jumlahDiprosesTemp = + daftarPengaduan.where((p) => p['status'] == 'Diproses').length; + int jumlahTindakanTemp = + daftarPengaduan.where((p) => p['status'] == 'Tindakan').length; + int jumlahSelesaiTemp = + daftarPengaduan.where((p) => p['status'] == 'Selesai').length; + + // Update nilai Rx + jumlahDiproses.value = jumlahDiprosesTemp; + jumlahTindakan.value = jumlahTindakanTemp; + jumlahSelesai.value = jumlahSelesaiTemp; + + // Print untuk debugging + print('Data pengaduan dimuat:'); + print('Jumlah pengaduan diproses: ${jumlahDiproses.value}'); + print('Jumlah pengaduan tindakan: ${jumlahTindakan.value}'); + print('Jumlah pengaduan selesai: ${jumlahSelesai.value}'); + print('Total pengaduan: ${daftarPengaduan.length}'); + + // Perbarui UI secara manual + update(); + + // Di implementasi nyata, data akan diambil dari Supabase + // final result = await _supabaseService.getPengaduanData(); + // daftarPengaduan.value = result ?? []; + // jumlahDiproses.value = daftarPengaduan.where((p) => p['status'] == 'Diproses').length; + // jumlahTindakan.value = daftarPengaduan.where((p) => p['status'] == 'Tindakan').length; + // jumlahSelesai.value = daftarPengaduan.where((p) => p['status'] == 'Selesai').length; + } catch (e) { + print('Error loading pengaduan data: $e'); + } + } + + // Method untuk memperbarui jumlah pengaduan secara manual (untuk debugging) + void updatePengaduanCounter() { + jumlahDiproses.value = 5; // Set nilai secara manual + update(); // Perbarui UI + print( + 'Counter pengaduan diperbarui secara manual: ${jumlahDiproses.value}'); + } + void tandaiNotifikasiDibaca(String id) { // Implementasi untuk menandai notifikasi sebagai dibaca // Di implementasi nyata, akan memanggil Supabase untuk memperbarui status notifikasi @@ -394,6 +570,24 @@ class PetugasDesaController extends GetxController { loadPenitipanData(); } + void prosesPengaduan(String id, String tindakan) { + // Implementasi untuk memproses pengaduan + // Di implementasi nyata, akan memanggil Supabase untuk memperbarui status pengaduan + // await _supabaseService.processPengaduan(id, tindakan); + + // Perbarui data lokal + loadPengaduanData(); + } + + void selesaikanPengaduan(String id, String hasil) { + // Implementasi untuk menyelesaikan pengaduan + // Di implementasi nyata, akan memanggil Supabase untuk memperbarui status pengaduan + // await _supabaseService.completePengaduan(id, hasil); + + // Perbarui data lokal + loadPengaduanData(); + } + void logout() { _authController.logout(); } @@ -401,4 +595,82 @@ class PetugasDesaController extends GetxController { void changeTab(int index) { activeTabIndex.value = index; } + + // Metode untuk konfirmasi permintaan penjadwalan + Future konfirmasiPermintaanPenjadwalan( + String id, String jadwalId) async { + try { + if (id.isEmpty || jadwalId.isEmpty) { + Get.snackbar( + 'Error', + 'ID permintaan atau jadwal tidak valid', + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + isLoading.value = true; + + // Simulasi proses konfirmasi + await Future.delayed(const Duration(milliseconds: 800)); + + // Hapus permintaan dari daftar + permintaanPenjadwalan.removeWhere((item) => item['id'] == id); + jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length; + + // Di implementasi nyata, data akan diupdate ke Supabase + // await _supabaseService.konfirmasiPermintaanPenjadwalan(id, jadwalId); + // await loadPermintaanPenjadwalanData(); + // await loadJadwalData(); + } catch (e) { + print('Error konfirmasi permintaan penjadwalan: $e'); + Get.snackbar( + 'Error', + 'Terjadi kesalahan saat mengkonfirmasi permintaan', + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } finally { + isLoading.value = false; + } + } + + // Metode untuk menolak permintaan penjadwalan + Future tolakPermintaanPenjadwalan(String id, String alasan) async { + try { + if (id.isEmpty) { + Get.snackbar( + 'Error', + 'ID permintaan tidak valid', + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + isLoading.value = true; + + // Simulasi proses penolakan + await Future.delayed(const Duration(milliseconds: 800)); + + // Hapus permintaan dari daftar + permintaanPenjadwalan.removeWhere((item) => item['id'] == id); + jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length; + + // Di implementasi nyata, data akan diupdate ke Supabase + // await _supabaseService.tolakPermintaanPenjadwalan(id, alasan); + // await loadPermintaanPenjadwalanData(); + } catch (e) { + print('Error tolak permintaan penjadwalan: $e'); + Get.snackbar( + 'Error', + 'Terjadi kesalahan saat menolak permintaan', + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } finally { + isLoading.value = false; + } + } } diff --git a/lib/app/modules/petugas_desa/views/jadwal_view.dart b/lib/app/modules/petugas_desa/views/jadwal_view.dart deleted file mode 100644 index 23efc41..0000000 --- a/lib/app/modules/petugas_desa/views/jadwal_view.dart +++ /dev/null @@ -1,289 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; -import 'package:penyaluran_app/app/theme/app_theme.dart'; - -class JadwalView extends GetView { - const JadwalView({super.key}); - - @override - Widget build(BuildContext context) { - final textTheme = Theme.of(context).textTheme; - - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Ringkasan jadwal - _buildJadwalSummary(context), - - const SizedBox(height: 20), - // Jadwal hari ini - _buildJadwalSection( - textTheme, - title: 'Hari Ini', - jadwalList: [ - { - 'lokasi': 'Kantor Kepala Desa', - 'jenisBantuan': 'Beras', - 'tanggal': '15 April 2023', - 'waktu': '13:00 - 14:00', - 'status': 'Aktif', - }, - ], - ), - - const SizedBox(height: 20), - - // Jadwal mendatang - _buildJadwalSection( - textTheme, - title: 'Mendatang', - jadwalList: [ - { - 'lokasi': 'Balai Desa A', - 'jenisBantuan': 'Sembako', - 'tanggal': '17 April 2023', - 'waktu': '13:00 - 14:00', - 'status': 'Terjadwal', - }, - { - 'lokasi': 'Balai Desa B', - 'jenisBantuan': 'Uang Tunai', - 'tanggal': '20 April 2023', - 'waktu': '10:00 - 12:00', - 'status': 'Terjadwal', - }, - ], - ), - - const SizedBox(height: 20), - - // Jadwal selesai - _buildJadwalSection( - textTheme, - title: 'Selesai', - jadwalList: [ - { - 'lokasi': 'Kantor Kepala Desa', - 'jenisBantuan': 'Beras', - 'tanggal': '10 April 2023', - 'waktu': '13:00 - 14:00', - 'status': 'Selesai', - }, - { - 'lokasi': 'Balai Desa C', - 'jenisBantuan': 'Sembako', - 'tanggal': '5 April 2023', - 'waktu': '09:00 - 11:00', - 'status': 'Selesai', - }, - ], - ), - ], - ), - ), - ); - } - - Widget _buildJadwalSummary(BuildContext context) { - 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 Jadwal', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _buildSummaryItem( - context, - icon: Icons.pending_actions, - title: 'Terjadwal', - value: '5', - color: Colors.blue, - ), - ), - Expanded( - child: _buildSummaryItem( - context, - icon: Icons.event_available, - title: 'Aktif', - value: '1', - color: Colors.green, - ), - ), - Expanded( - child: _buildSummaryItem( - context, - icon: Icons.event_busy, - title: 'Selesai', - value: '12', - color: Colors.grey, - ), - ), - ], - ), - ], - ), - ); - } - - Widget _buildSummaryItem( - BuildContext context, { - required IconData icon, - required String title, - required String value, - required Color color, - }) { - return Column( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - shape: BoxShape.circle, - ), - child: Icon( - icon, - color: Colors.white, - size: 24, - ), - ), - const SizedBox(height: 8), - Text( - value, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 4), - Text( - title, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - ], - ); - } - - Widget _buildJadwalSection( - TextTheme textTheme, { - required String title, - required List> jadwalList, - }) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - ...jadwalList.map((jadwal) => _buildJadwalItem(textTheme, jadwal)), - ], - ); - } - - Widget _buildJadwalItem(TextTheme textTheme, Map jadwal) { - Color statusColor; - switch (jadwal['status']) { - case 'Aktif': - statusColor = Colors.green; - break; - case 'Terjadwal': - statusColor = Colors.blue; - break; - case 'Selesai': - statusColor = Colors.grey; - break; - default: - statusColor = Colors.orange; - } - - return Container( - width: double.infinity, - margin: const EdgeInsets.only(bottom: 10), - 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( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - jadwal['lokasi'] ?? '', - style: textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: statusColor.withAlpha(26), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - jadwal['status'] ?? '', - style: textTheme.bodySmall?.copyWith( - color: statusColor, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - 'Jenis Bantuan: ${jadwal['jenisBantuan'] ?? ''}', - style: textTheme.bodyMedium, - ), - const SizedBox(height: 4), - Text( - 'Tanggal: ${jadwal['tanggal'] ?? ''}', - style: textTheme.bodyMedium, - ), - const SizedBox(height: 4), - Text( - 'Waktu: ${jadwal['waktu'] ?? ''}', - style: textTheme.bodyMedium, - ), - ], - ), - ), - ); - } -} diff --git a/lib/app/modules/petugas_desa/views/pengaduan_view.dart b/lib/app/modules/petugas_desa/views/pengaduan_view.dart new file mode 100644 index 0000000..c5a9673 --- /dev/null +++ b/lib/app/modules/petugas_desa/views/pengaduan_view.dart @@ -0,0 +1,649 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class PengaduanView extends GetView { + const PengaduanView({super.key}); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Ringkasan pengaduan + _buildPengaduanSummary(context), + + const SizedBox(height: 24), + + // Filter dan pencarian + _buildFilterSearch(context), + + const SizedBox(height: 20), + + // Daftar pengaduan + _buildPengaduanList(context), + ], + ), + ), + ); + } + + Widget _buildPengaduanSummary(BuildContext context) { + 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, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: _buildSummaryItem( + context, + icon: Icons.pending_actions, + title: 'Diproses', + value: '3', + color: Colors.orange, + ), + ), + Expanded( + child: _buildSummaryItem( + context, + icon: Icons.engineering, + title: 'Tindakan', + value: '2', + color: Colors.blue, + ), + ), + Expanded( + child: _buildSummaryItem( + context, + icon: Icons.check_circle, + title: 'Selesai', + value: '8', + color: Colors.green, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSummaryItem( + BuildContext context, { + required IconData icon, + required String title, + required String value, + required Color color, + }) { + return Column( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + icon, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(height: 8), + Text( + value, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Text( + title, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + ], + ); + } + + Widget _buildFilterSearch(BuildContext context) { + return Row( + children: [ + Expanded( + child: TextField( + decoration: InputDecoration( + hintText: 'Cari 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), + ), + ), + ), + const SizedBox(width: 12), + Container( + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: IconButton( + onPressed: () { + // Tampilkan dialog filter + _showFilterDialog(context); + }, + icon: const Icon(Icons.filter_list), + tooltip: 'Filter', + ), + ), + ], + ); + } + + void _showFilterDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Filter Pengaduan'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CheckboxListTile( + title: const Text('Diproses'), + value: true, + onChanged: (value) {}, + ), + CheckboxListTile( + title: const Text('Tindakan'), + value: true, + onChanged: (value) {}, + ), + CheckboxListTile( + title: const Text('Selesai'), + value: true, + onChanged: (value) {}, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context), + child: const Text('Terapkan'), + ), + ], + ), + ); + } + + Widget _buildPengaduanList(BuildContext context) { + final List> pengaduanList = [ + { + 'id': '1', + 'nama': 'Budi Santoso', + 'nik': '3201020107030011', + 'jenis_pengaduan': 'Bantuan Tidak Diterima', + 'deskripsi': + 'Saya belum menerima bantuan beras yang dijadwalkan minggu lalu', + 'tanggal': '15 April 2023', + 'status': 'Diproses', + }, + { + 'id': '2', + 'nama': 'Siti Rahayu', + 'nik': '3201020107030010', + 'jenis_pengaduan': 'Kualitas Bantuan', + 'deskripsi': + 'Beras yang diterima berkualitas buruk dan tidak layak konsumsi', + 'tanggal': '14 April 2023', + 'status': 'Tindakan', + }, + { + 'id': '3', + 'nama': 'Ahmad Fauzi', + 'nik': '3201020107030013', + 'jenis_pengaduan': 'Jumlah Bantuan', + 'deskripsi': + 'Jumlah bantuan yang diterima tidak sesuai dengan yang dijanjikan', + 'tanggal': '13 April 2023', + 'status': 'Tindakan', + }, + { + 'id': '4', + 'nama': 'Dewi Lestari', + 'nik': '3201020107030012', + 'jenis_pengaduan': 'Jadwal Penyaluran', + 'deskripsi': + 'Jadwal penyaluran bantuan sering berubah tanpa pemberitahuan', + 'tanggal': '10 April 2023', + 'status': 'Selesai', + }, + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Daftar Pengaduan', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + ...pengaduanList.map((item) => _buildPengaduanItem(context, item)), + ], + ); + } + + Widget _buildPengaduanItem(BuildContext context, Map item) { + Color statusColor; + IconData statusIcon; + + switch (item['status']) { + case 'Diproses': + statusColor = Colors.orange; + statusIcon = Icons.pending_actions; + break; + case 'Tindakan': + statusColor = Colors.blue; + statusIcon = Icons.engineering; + break; + case 'Selesai': + statusColor = Colors.green; + statusIcon = Icons.check_circle; + break; + default: + statusColor = Colors.grey; + statusIcon = Icons.help_outline; + } + + return 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( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + item['nama'] ?? '', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(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, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + 'NIK: ${item['nik'] ?? ''}', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey, + ), + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + item['jenis_pengaduan'] ?? '', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 8), + Text( + item['deskripsi'] ?? '', + style: Theme.of(context).textTheme.bodyMedium, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.calendar_today, + size: 14, + color: Colors.grey, + ), + const SizedBox(width: 4), + Text( + item['tanggal'] ?? '', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey, + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: _buildActionButtons(context, item), + ), + ], + ), + ), + ); + } + + List _buildActionButtons( + BuildContext context, Map item) { + final status = item['status']; + + if (status == 'Diproses') { + 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), + ), + ), + TextButton.icon( + onPressed: () { + // Implementasi untuk melihat detail pengaduan + _showDetailDialog(context, item); + }, + 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: () { + // Implementasi untuk melihat detail pengaduan + _showDetailDialog(context, item); + }, + 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: () { + // Implementasi untuk melihat detail pengaduan + _showDetailDialog(context, item); + }, + 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 _showDetailDialog(BuildContext context, Map item) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Detail Pengaduan: ${item['id']}'), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildDetailItem('Nama', item['nama'] ?? ''), + _buildDetailItem('NIK', item['nik'] ?? ''), + _buildDetailItem( + 'Jenis Pengaduan', item['jenis_pengaduan'] ?? ''), + _buildDetailItem('Tanggal', item['tanggal'] ?? ''), + _buildDetailItem('Status', item['status'] ?? ''), + const SizedBox(height: 8), + const Text( + 'Deskripsi:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + Text(item['deskripsi'] ?? ''), + if (item['status'] == 'Tindakan' || + item['status'] == 'Selesai') ...[ + const SizedBox(height: 8), + const Text( + 'Tindakan:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + Text(item['tindakan'] ?? + 'Pengecekan ke lokasi dan verifikasi data penerima'), + ], + if (item['status'] == 'Selesai') ...[ + const SizedBox(height: 8), + const Text( + 'Hasil:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + Text(item['hasil'] ?? + 'Pengaduan telah diselesaikan dengan penyaluran ulang bantuan'), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Tutup'), + ), + ], + ), + ); + } + + Widget _buildDetailItem(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + '$label:', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + Expanded(child: Text(value)), + ], + ), + ); + } + + void _showTindakanDialog(BuildContext context, Map item) { + final TextEditingController tindakanController = TextEditingController(); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Tindakan Pengaduan'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Pengaduan dari: ${item['nama']}'), + const SizedBox(height: 16), + TextField( + controller: tindakanController, + decoration: const InputDecoration( + labelText: 'Tindakan yang dilakukan', + border: OutlineInputBorder(), + ), + maxLines: 3, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () { + // Implementasi untuk menyimpan tindakan + Navigator.pop(context); + Get.snackbar( + 'Berhasil', + 'Status pengaduan berhasil diubah menjadi Tindakan', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.blue, + colorText: Colors.white, + ); + }, + child: const Text('Simpan'), + ), + ], + ), + ); + } + + void _showSelesaikanDialog(BuildContext context, Map item) { + final TextEditingController hasilController = TextEditingController(); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Selesaikan Pengaduan'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Pengaduan dari: ${item['nama']}'), + const SizedBox(height: 16), + TextField( + controller: hasilController, + decoration: const InputDecoration( + labelText: 'Hasil penyelesaian', + border: OutlineInputBorder(), + ), + maxLines: 3, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () { + // Implementasi untuk menyimpan hasil + Navigator.pop(context); + Get.snackbar( + 'Berhasil', + 'Status pengaduan berhasil diubah menjadi Selesai', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + }, + child: const Text('Selesaikan'), + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/views/penyaluran_view.dart b/lib/app/modules/petugas_desa/views/penyaluran_view.dart new file mode 100644 index 0000000..8016ce1 --- /dev/null +++ b/lib/app/modules/petugas_desa/views/penyaluran_view.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/components/jadwal_section_widget.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart'; + +class PenyaluranView extends GetView { + const PenyaluranView({super.key}); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Ringkasan jadwal + _buildJadwalSummary(context), + + const SizedBox(height: 20), + + // Ringkasan Permintaan Penjadwalan + PermintaanPenjadwalanSummaryWidget(controller: controller), + + const SizedBox(height: 20), + + // Jadwal hari ini + JadwalSectionWidget( + controller: controller, + title: 'Hari Ini', + jadwalList: controller.jadwalHariIni, + status: 'Aktif', + ), + + const SizedBox(height: 20), + + // Jadwal mendatang + JadwalSectionWidget( + controller: controller, + title: 'Mendatang', + jadwalList: controller.jadwalMendatang, + status: 'Terjadwal', + ), + + const SizedBox(height: 20), + + // Jadwal selesai + JadwalSectionWidget( + controller: controller, + title: 'Selesai', + jadwalList: controller.jadwalSelesai, + status: 'Selesai', + ), + ], + ), + ), + ); + } + + Widget _buildJadwalSummary(BuildContext context) { + 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 Jadwal', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: Obx(() => _buildSummaryItem( + context, + icon: Icons.pending_actions, + title: 'Terjadwal', + value: '${controller.jadwalMendatang.length}', + color: Colors.blue, + )), + ), + Expanded( + child: Obx(() => _buildSummaryItem( + context, + icon: Icons.event_available, + title: 'Aktif', + value: '${controller.jadwalHariIni.length}', + color: Colors.green, + )), + ), + Expanded( + child: Obx(() => _buildSummaryItem( + context, + icon: Icons.event_busy, + title: 'Selesai', + value: '${controller.jadwalSelesai.length}', + color: Colors.grey, + )), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSummaryItem( + BuildContext context, { + required IconData icon, + required String title, + required String value, + required Color color, + }) { + return Column( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + icon, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(height: 8), + Text( + value, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Text( + title, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + ], + ); + } +} diff --git a/lib/app/modules/petugas_desa/views/permintaan_penjadwalan_view.dart b/lib/app/modules/petugas_desa/views/permintaan_penjadwalan_view.dart new file mode 100644 index 0000000..b9d53d5 --- /dev/null +++ b/lib/app/modules/petugas_desa/views/permintaan_penjadwalan_view.dart @@ -0,0 +1,341 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class PermintaanPenjadwalanView extends GetView { + const PermintaanPenjadwalanView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + // Pastikan controller sudah diinisialisasi + if (!Get.isRegistered()) { + Get.put(PetugasDesaController()); + } + + return Scaffold( + appBar: AppBar( + title: const Text('Permintaan Penjadwalan'), + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Get.back(), + ), + ), + body: Obx(() { + final permintaanList = controller.permintaanPenjadwalan; + + if (permintaanList.isEmpty) { + return _buildEmptyState(); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: permintaanList.length, + itemBuilder: (context, index) { + final permintaan = permintaanList[index]; + return _buildPermintaanItem(context, permintaan); + }, + ); + }), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.event_note, + size: 80, + color: Colors.grey.shade400, + ), + const SizedBox(height: 16), + Text( + 'Tidak ada permintaan penjadwalan', + style: TextStyle( + fontSize: 18, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + 'Semua permintaan penjadwalan akan muncul di sini', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade500, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + Widget _buildPermintaanItem( + BuildContext context, Map permintaan) { + final textTheme = Theme.of(context).textTheme; + + return Card( + margin: const EdgeInsets.only(bottom: 16), + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: Colors.orange.withAlpha(50), + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + permintaan['nama'] ?? '', + style: textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.orange.withAlpha(26), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Menunggu', + style: textTheme.bodySmall?.copyWith( + color: Colors.orange, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + _buildInfoRow(Icons.person, 'NIK: ${permintaan['nik'] ?? ''}'), + _buildInfoRow(Icons.category, + 'Jenis Bantuan: ${permintaan['jenis_bantuan'] ?? ''}'), + _buildInfoRow(Icons.calendar_today, + 'Tanggal Permintaan: ${permintaan['tanggal_permintaan'] ?? ''}'), + _buildInfoRow( + Icons.location_on, 'Alamat: ${permintaan['alamat'] ?? ''}'), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton.icon( + onPressed: () => _showTolakDialog(permintaan), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.red, + side: const BorderSide(color: Colors.red), + ), + icon: const Icon(Icons.close), + label: const Text('Tolak'), + ), + const SizedBox(width: 12), + ElevatedButton.icon( + onPressed: () => _showKonfirmasiDialog(permintaan), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + ), + icon: const Icon(Icons.check), + label: const Text('Konfirmasi'), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildInfoRow(IconData icon, String text) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Icon( + icon, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + text, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade800, + ), + ), + ), + ], + ), + ); + } + + // Dialog untuk konfirmasi permintaan + void _showKonfirmasiDialog(Map permintaan) { + String? selectedJadwalId; + + // Data jadwal yang tersedia dari controller + final jadwalOptions = controller.jadwalMendatang.map((jadwal) { + return DropdownMenuItem( + value: jadwal['id'], + child: Text( + '${jadwal['tanggal'] ?? ''} - ${jadwal['lokasi'] ?? ''} (${jadwal['jenis_bantuan'] ?? ''})'), + ); + }).toList(); + + // Tambahkan opsi jadwal lain jika diperlukan + jadwalOptions.add( + const DropdownMenuItem( + value: '3', + child: Text('25 April 2023 - Kantor Kepala Desa (Beras)'), + ), + ); + + Get.dialog( + AlertDialog( + title: const Text('Konfirmasi Permintaan'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan['nama'] ?? 'Penerima'}.'), + const SizedBox(height: 16), + const Text('Pilih jadwal penyaluran:'), + const SizedBox(height: 8), + DropdownButtonFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: + EdgeInsets.symmetric(horizontal: 12, vertical: 8), + ), + items: jadwalOptions, + onChanged: (value) { + selectedJadwalId = value; + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () { + if (selectedJadwalId != null) { + // Panggil metode konfirmasi di controller + controller.konfirmasiPermintaanPenjadwalan( + permintaan['id'] ?? '', + selectedJadwalId ?? '', + ); + + Get.back(); + Get.snackbar( + 'Berhasil', + 'Permintaan penjadwalan berhasil dikonfirmasi', + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } else { + Get.snackbar( + 'Peringatan', + 'Silakan pilih jadwal penyaluran terlebih dahulu', + backgroundColor: Colors.orange, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + ), + child: const Text('Konfirmasi'), + ), + ], + ), + ); + } + + // Dialog untuk menolak permintaan + void _showTolakDialog(Map permintaan) { + final TextEditingController alasanController = TextEditingController(); + + Get.dialog( + AlertDialog( + title: const Text('Tolak Permintaan'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Anda akan menolak permintaan penjadwalan dari ${permintaan['nama'] ?? 'Penerima'}.'), + const SizedBox(height: 16), + const Text('Alasan penolakan:'), + const SizedBox(height: 8), + TextField( + controller: alasanController, + maxLines: 3, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Masukkan alasan penolakan', + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Batal'), + ), + ElevatedButton( + onPressed: () { + if (alasanController.text.trim().isNotEmpty) { + // Panggil metode tolak di controller + controller.tolakPermintaanPenjadwalan( + permintaan['id'] ?? '', + alasanController.text.trim(), + ); + + Get.back(); + Get.snackbar( + 'Berhasil', + 'Permintaan penjadwalan berhasil ditolak', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } else { + Get.snackbar( + 'Peringatan', + 'Silakan masukkan alasan penolakan', + backgroundColor: Colors.orange, + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + child: const Text('Tolak'), + ), + ], + ), + ); + } +} 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 e1e1f0e..db850e5 100644 --- a/lib/app/modules/petugas_desa/views/petugas_desa_view.dart +++ b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart @@ -2,10 +2,11 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/dashboard_view.dart'; -import 'package:penyaluran_app/app/modules/petugas_desa/views/jadwal_view.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/views/penyaluran_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/notifikasi_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/inventaris_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/penitipan_view.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/views/pengaduan_view.dart'; import 'package:penyaluran_app/app/theme/app_theme.dart'; class PetugasDesaView extends GetView { @@ -15,6 +16,12 @@ class PetugasDesaView extends GetView { Widget build(BuildContext context) { final GlobalKey scaffoldKey = GlobalKey(); + // Perbarui counter pengaduan secara manual saat aplikasi dimulai + WidgetsBinding.instance.addPostFrameCallback((_) { + controller.updatePengaduanCounter(); + print('Counter pengaduan diperbarui saat aplikasi dimulai'); + }); + return Scaffold( key: scaffoldKey, appBar: AppBar( @@ -23,11 +30,13 @@ class PetugasDesaView extends GetView { case 0: return const Text('Dashboard'); case 1: - return const Text('Jadwal Penyaluran'); + return const Text('Penyaluran'); case 2: - return const Text('Inventaris'); - case 3: return const Text('Penitipan'); + case 3: + return const Text('Pengaduan'); + case 4: + return const Text('Inventaris'); default: return const Text('Petugas Desa'); } @@ -115,6 +124,35 @@ class PetugasDesaView extends GetView { notificationButton, ], ); + } else if (activeTab == 4) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Tambah Pengaduan', + onPressed: () { + // Implementasi untuk menambah pengaduan baru + }, + ), + IconButton( + icon: const Icon(Icons.filter_list), + tooltip: 'Filter Pengaduan', + onPressed: () { + // Implementasi untuk filter pengaduan + }, + ), + IconButton( + icon: const Icon(Icons.refresh), + tooltip: 'Perbarui Counter', + onPressed: () { + // Perbarui counter pengaduan secara manual + controller.updatePengaduanCounter(); + }, + ), + notificationButton, + ], + ); } else { return notificationButton; } @@ -127,11 +165,14 @@ class PetugasDesaView extends GetView { case 0: return const DashboardView(); case 1: - return const JadwalView(); + return const PenyaluranView(); + case 2: - return const InventarisView(); - case 3: return const PenitipanView(); + case 3: + return const PengaduanView(); + case 4: + return const InventarisView(); default: return const DashboardView(); } @@ -193,7 +234,7 @@ class PetugasDesaView extends GetView { )), Obx(() => ListTile( leading: const Icon(Icons.calendar_today_outlined), - title: const Text('Jadwal Penyaluran'), + title: const Text('Penyaluran'), selected: controller.activeTabIndex.value == 1, selectedColor: AppTheme.primaryColor, onTap: () { @@ -201,16 +242,6 @@ class PetugasDesaView extends GetView { Navigator.pop(context); }, )), - Obx(() => ListTile( - leading: const Icon(Icons.inventory_2_outlined), - title: const Text('Inventaris'), - selected: controller.activeTabIndex.value == 2, - selectedColor: AppTheme.primaryColor, - onTap: () { - controller.changeTab(2); - Navigator.pop(context); - }, - )), Obx(() => ListTile( leading: Stack( alignment: Alignment.center, @@ -243,10 +274,65 @@ class PetugasDesaView extends GetView { ], ), title: const Text('Penitipan'), - selected: controller.activeTabIndex.value == 3, + selected: controller.activeTabIndex.value == 2, selectedColor: AppTheme.primaryColor, onTap: () { - controller.changeTab(3); + controller.changeTab(2); + Navigator.pop(context); + }, + )), + Obx(() { + final int jumlahPengaduanDiproses = controller.jumlahDiproses.value; + print( + 'Drawer - Jumlah pengaduan diproses: $jumlahPengaduanDiproses'); + + return ListTile( + leading: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.report_problem_outlined), + // Selalu tampilkan badge untuk debugging + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(10), + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + jumlahPengaduanDiproses.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + title: const Text('Pengaduan'), + selected: controller.activeTabIndex.value == 3, + selectedColor: AppTheme.primaryColor, + onTap: () { + controller.changeTab(3); + Navigator.pop(context); + }, + ); + }), + Obx(() => ListTile( + leading: const Icon(Icons.inventory_2_outlined), + title: const Text('Inventaris'), + selected: controller.activeTabIndex.value == 4, + selectedColor: AppTheme.primaryColor, + onTap: () { + controller.changeTab(4); Navigator.pop(context); }, )), @@ -323,150 +409,222 @@ class PetugasDesaView extends GetView { } Widget _buildBottomNavigationBar() { - return Obx(() => BottomNavigationBar( - currentIndex: controller.activeTabIndex.value, - onTap: controller.changeTab, - type: BottomNavigationBarType.fixed, - selectedItemColor: AppTheme.primaryColor, - unselectedItemColor: Colors.grey, - items: [ - const BottomNavigationBarItem( - icon: Icon(Icons.dashboard_outlined), - activeIcon: Icon(Icons.dashboard), - label: 'Dashboard', - ), - BottomNavigationBarItem( - icon: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.calendar_today_outlined), - if (controller.jadwalHariIni.isNotEmpty) - Positioned( - top: 0, - right: 0, - child: Container( - padding: const EdgeInsets.all(2), - decoration: const BoxDecoration( - color: Colors.blue, - shape: BoxShape.circle, - ), - constraints: const BoxConstraints( - minWidth: 12, - minHeight: 12, - ), - child: Text( - controller.jadwalHariIni.length.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 8, - ), - textAlign: TextAlign.center, + // Tambahkan print statement untuk debugging + print('Jumlah pengaduan diproses: ${controller.jumlahDiproses.value}'); + print('Jumlah jadwal hari ini: ${controller.jadwalHariIni.length}'); + + return Obx(() { + // Hitung jumlah pengaduan yang diproses + final int jumlahPengaduanDiproses = controller.jumlahDiproses.value; + + return BottomNavigationBar( + currentIndex: controller.activeTabIndex.value, + onTap: controller.changeTab, + type: BottomNavigationBarType.fixed, + selectedItemColor: AppTheme.primaryColor, + unselectedItemColor: Colors.grey, + items: [ + const BottomNavigationBarItem( + icon: Icon(Icons.dashboard_outlined), + activeIcon: Icon(Icons.dashboard), + label: 'Dashboard', + ), + BottomNavigationBarItem( + icon: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.calendar_today_outlined), + if (controller.jadwalHariIni.isNotEmpty) + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + controller.jadwalHariIni.length.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, ), + textAlign: TextAlign.center, ), ), - ], - ), - activeIcon: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.calendar_today), - if (controller.jadwalHariIni.isNotEmpty) - Positioned( - top: 0, - right: 0, - child: Container( - padding: const EdgeInsets.all(2), - decoration: const BoxDecoration( - color: Colors.blue, - shape: BoxShape.circle, - ), - constraints: const BoxConstraints( - minWidth: 12, - minHeight: 12, - ), - child: Text( - controller.jadwalHariIni.length.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 8, - ), - textAlign: TextAlign.center, + ), + ], + ), + activeIcon: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.calendar_today), + if (controller.jadwalHariIni.isNotEmpty) + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + controller.jadwalHariIni.length.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, ), + textAlign: TextAlign.center, ), ), - ], - ), - label: 'Jadwal', + ), + ], ), - const BottomNavigationBarItem( - icon: Icon(Icons.inventory_2_outlined), - activeIcon: Icon(Icons.inventory_2), - label: 'Inventaris', - ), - BottomNavigationBarItem( - icon: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.handshake_outlined), - if (controller.jumlahMenunggu.value > 0) - Positioned( - top: 0, - right: 0, - child: Container( - padding: const EdgeInsets.all(2), - decoration: const BoxDecoration( - color: Colors.orange, - shape: BoxShape.circle, - ), - constraints: const BoxConstraints( - minWidth: 12, - minHeight: 12, - ), - child: Text( - controller.jumlahMenunggu.value.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 8, - ), - textAlign: TextAlign.center, + label: 'Penyaluran', + ), + BottomNavigationBarItem( + icon: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.handshake_outlined), + if (controller.jumlahMenunggu.value > 0) + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.orange, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + controller.jumlahMenunggu.value.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, ), + textAlign: TextAlign.center, ), ), - ], - ), - activeIcon: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.handshake), - if (controller.jumlahMenunggu.value > 0) - Positioned( - top: 0, - right: 0, - child: Container( - padding: const EdgeInsets.all(2), - decoration: const BoxDecoration( - color: Colors.orange, - shape: BoxShape.circle, - ), - constraints: const BoxConstraints( - minWidth: 12, - minHeight: 12, - ), - child: Text( - controller.jumlahMenunggu.value.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 8, - ), - textAlign: TextAlign.center, + ), + ], + ), + activeIcon: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.handshake), + if (controller.jumlahMenunggu.value > 0) + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.orange, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + controller.jumlahMenunggu.value.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, ), + textAlign: TextAlign.center, ), ), - ], - ), - label: 'Penitipan', + ), + ], ), - ], - )); + label: 'Penitipan', + ), + BottomNavigationBarItem( + icon: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.report_problem_outlined), + // Selalu tampilkan badge untuk debugging + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + controller.jumlahDiproses.value.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + activeIcon: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.report_problem), + // Selalu tampilkan badge untuk debugging + Positioned( + top: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 12, + minHeight: 12, + ), + child: Text( + controller.jumlahDiproses.value.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 8, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + label: 'Pengaduan', + ), + const BottomNavigationBarItem( + icon: Icon(Icons.inventory_2_outlined), + activeIcon: Icon(Icons.inventory_2), + label: 'Inventaris', + ), + ], + ); + }); } } diff --git a/lib/app/modules/splash/bindings/splash_binding.dart b/lib/app/modules/splash/bindings/splash_binding.dart new file mode 100644 index 0000000..f38fe7a --- /dev/null +++ b/lib/app/modules/splash/bindings/splash_binding.dart @@ -0,0 +1,8 @@ +import 'package:get/get.dart'; + +class SplashBinding extends Bindings { + @override + void dependencies() { + // Tidak perlu menambahkan controller khusus untuk splash screen + } +} diff --git a/lib/app/modules/splash/views/splash_view.dart b/lib/app/modules/splash/views/splash_view.dart new file mode 100644 index 0000000..b1eb1bb --- /dev/null +++ b/lib/app/modules/splash/views/splash_view.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/routes/app_pages.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; + +class SplashView extends StatefulWidget { + const SplashView({Key? key}) : super(key: key); + + @override + State createState() => _SplashViewState(); +} + +class _SplashViewState extends State { + @override + void initState() { + super.initState(); + _navigateToLogin(); + } + + _navigateToLogin() async { + await Future.delayed(const Duration(seconds: 2)); + Get.offAllNamed(Routes.login); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: AppTheme.primaryGradient, + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/logo.png', + width: 120, + height: 120, + errorBuilder: (context, error, stackTrace) { + return Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + child: const Icon( + Icons.people, + size: 60, + color: AppTheme.primaryColor, + ), + ); + }, + ), + const SizedBox(height: 24), + const Text( + 'Aplikasi Penyaluran', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + const Text( + 'Bantuan Sosial', + style: TextStyle( + fontSize: 18, + color: Colors.white, + ), + ), + const SizedBox(height: 48), + const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 13433b3..e097034 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -9,6 +9,7 @@ import 'package:penyaluran_app/app/modules/home/bindings/home_binding.dart'; import 'package:penyaluran_app/app/modules/dashboard/bindings/dashboard_binding.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/petugas_desa_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/petugas_desa_binding.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/views/permintaan_penjadwalan_view.dart'; part 'app_routes.dart'; @@ -48,5 +49,10 @@ class AppPages { page: () => const DonaturDashboardView(), binding: DashboardBinding(), ), + GetPage( + name: _Paths.permintaanPenjadwalan, + page: () => const PermintaanPenjadwalanView(), + binding: PetugasDesaBinding(), + ), ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 338d73e..e2aece7 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -9,6 +9,8 @@ abstract class Routes { static const petugasVerifikasiDashboard = _Paths.petugasVerifikasiDashboard; static const petugasDesaDashboard = _Paths.petugasDesaDashboard; static const donaturDashboard = _Paths.donaturDashboard; + static const splash = _Paths.splash; + static const permintaanPenjadwalan = _Paths.permintaanPenjadwalan; } abstract class _Paths { @@ -20,4 +22,6 @@ abstract class _Paths { static const petugasVerifikasiDashboard = '/petugas-verifikasi-dashboard'; static const petugasDesaDashboard = '/petugas-desa-dashboard'; static const donaturDashboard = '/donatur-dashboard'; + static const splash = '/splash'; + static const permintaanPenjadwalan = '/permintaan-penjadwalan'; }