diff --git a/lib/app/modules/petugas_desa/bindings/pelaksanaan_penyaluran_binding.dart b/lib/app/modules/petugas_desa/bindings/pelaksanaan_penyaluran_binding.dart new file mode 100644 index 0000000..90cddb1 --- /dev/null +++ b/lib/app/modules/petugas_desa/bindings/pelaksanaan_penyaluran_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/pelaksanaan_penyaluran_controller.dart'; + +class PelaksanaanPenyaluranBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => PelaksanaanPenyaluranController(), + ); + } +} diff --git a/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart index 4e95f27..8c8a20b 100644 --- a/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart +++ b/lib/app/modules/petugas_desa/components/jadwal_section_widget.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:intl/intl.dart'; import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart'; import 'package:penyaluran_app/app/routes/app_pages.dart'; @@ -204,24 +203,8 @@ class JadwalSectionWidget extends StatelessWidget { child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () { - // Konversi PenyaluranBantuanModel ke Map - final jadwalMap = { - 'id': jadwal.id, - 'nama': jadwal.nama, - 'deskripsi': jadwal.deskripsi, - 'lokasi': jadwal.nama, // Gunakan nama sebagai lokasi - 'kategori_bantuan': jadwal.kategoriBantuanId, - 'tanggal': jadwal.tanggalPenyaluran != null - ? DateTimeHelper.formatDate(jadwal.tanggalPenyaluran) - : '-', - 'waktu': jadwal.tanggalPenyaluran != null - ? DateTimeHelper.formatTime(jadwal.tanggalPenyaluran) - : '-', - 'jumlah_penerima': jadwal.jumlahPenerima, - 'status': jadwal.status, - }; - - Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwalMap); + // Hanya kirim ID penyaluran + Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwal.id); }, child: Padding( padding: const EdgeInsets.all(16), @@ -336,25 +319,9 @@ class JadwalSectionWidget extends StatelessWidget { alignment: Alignment.centerRight, child: TextButton.icon( onPressed: () { - // Konversi PenyaluranBantuanModel ke Map - final jadwalMap = { - 'id': jadwal.id, - 'nama': jadwal.nama, - 'deskripsi': jadwal.deskripsi, - 'lokasi': jadwal.nama, // Gunakan nama sebagai lokasi - 'kategori_bantuan': jadwal.kategoriBantuanId, - 'tanggal': jadwal.tanggalPenyaluran != null - ? DateTimeHelper.formatDate(jadwal.tanggalPenyaluran) - : '-', - 'waktu': jadwal.tanggalPenyaluran != null - ? DateTimeHelper.formatTime(jadwal.tanggalPenyaluran) - : '-', - 'jumlah_penerima': jadwal.jumlahPenerima, - 'status': jadwal.status, - }; - + // Hanya kirim ID penyaluran Get.toNamed(Routes.pelaksanaanPenyaluran, - arguments: jadwalMap); + arguments: jadwal.id); }, icon: const Icon(Icons.info_outline, size: 16), label: const Text('Lihat Detail'), 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 5728121..aad316c 100644 --- a/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart @@ -219,8 +219,6 @@ class PetugasDesaController extends GetxController { // Update counter _counterService.updatePengaduanCounter(diproses); - - print('Jumlah pengaduan diproses: $diproses'); } else { _counterService.updatePengaduanCounter(0); } diff --git a/lib/app/modules/petugas_desa/views/konfirmasi_penerima_view.dart b/lib/app/modules/petugas_desa/views/konfirmasi_penerima_view.dart index d614e24..9dc35c3 100644 --- a/lib/app/modules/petugas_desa/views/konfirmasi_penerima_view.dart +++ b/lib/app/modules/petugas_desa/views/konfirmasi_penerima_view.dart @@ -1,14 +1,26 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/pelaksanaan_penyaluran_controller.dart'; import 'package:penyaluran_app/app/theme/app_theme.dart'; -class KonfirmasiPenerimaView extends GetView { +class KonfirmasiPenerimaView extends GetView { const KonfirmasiPenerimaView({super.key}); @override Widget build(BuildContext context) { - final String id = Get.arguments as String; + // Ambil data dari arguments + final Map args = Get.arguments ?? {}; + + // Pastikan semua parameter yang diperlukan tersedia + final penerimaId = args['penerima_id'] ?? 0; + final String penyaluranId = args['penyaluran_id']?.toString() ?? ''; + final Map warga = + args['warga'] as Map? ?? {}; + final Map jadwal = + args['jadwal'] as Map? ?? {}; + final String statusPenerimaan = + args['status_penerimaan']?.toString() ?? 'BELUMMENERIMA'; + final dynamic jumlahBantuan = args['jumlah_bantuan'] ?? 1; return Obx(() { if (controller.isLoading.value) { @@ -22,19 +34,6 @@ class KonfirmasiPenerimaView extends GetView { ); } - final penerima = controller.getPenerimaById(id); - - if (penerima == null) { - return Scaffold( - appBar: AppBar( - title: const Text('Konfirmasi Penerima'), - ), - body: const Center( - child: Text('Data penerima tidak ditemukan'), - ), - ); - } - return Scaffold( appBar: AppBar( title: const Text('Konfirmasi Penerima'), @@ -47,27 +46,28 @@ class KonfirmasiPenerimaView extends GetView { child: Column( children: [ // Header dengan foto dan nama - _buildHeader(penerima), + _buildHeader(warga), // Detail informasi penerima - _buildDetailInfo(penerima), + _buildDetailInfo(warga), // Detail jadwal dan bantuan - _buildDetailJadwalBantuan(penerima), + _buildDetailJadwalBantuan(jadwal, jumlahBantuan), // Form konfirmasi - _buildKonfirmasiForm(context, penerima), + _buildKonfirmasiForm(context, penerimaId, penyaluranId), const SizedBox(height: 20), ], ), ), - bottomNavigationBar: _buildBottomButtons(penerima), + bottomNavigationBar: + _buildBottomButtons(penerimaId, penyaluranId, statusPenerimaan), ); }); } - Widget _buildHeader(Map penerima) { + Widget _buildHeader(Map warga) { return Container( width: double.infinity, padding: const EdgeInsets.all(16), @@ -80,11 +80,11 @@ class KonfirmasiPenerimaView extends GetView { CircleAvatar( radius: 40, backgroundColor: Colors.white, - child: penerima['foto'] != null + child: warga['foto_url'] != null ? ClipRRect( borderRadius: BorderRadius.circular(40), - child: Image.asset( - penerima['foto'], + child: Image.network( + warga['foto_url'], width: 80, height: 80, fit: BoxFit.cover, @@ -106,7 +106,7 @@ class KonfirmasiPenerimaView extends GetView { const SizedBox(height: 12), // Nama penerima Text( - penerima['nama'] ?? 'Nama tidak tersedia', + warga['nama'] ?? 'Nama tidak tersedia', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -116,7 +116,7 @@ class KonfirmasiPenerimaView extends GetView { const SizedBox(height: 4), // NIK Text( - penerima['nik'] ?? 'NIK tidak tersedia', + warga['nik'] ?? 'NIK tidak tersedia', style: const TextStyle( fontSize: 14, color: Colors.white, @@ -127,24 +127,23 @@ class KonfirmasiPenerimaView extends GetView { Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( - color: penerima['status'] == 'Terjadwal' - ? AppTheme.scheduledColor - : AppTheme.completedColor, + color: controller.getStatusColor( + warga['status_penerimaan'] ?? 'BELUMMENERIMA'), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( - penerima['status'] == 'Terjadwal' - ? Icons.event_available - : Icons.check_circle, + controller.getStatusIcon( + warga['status_penerimaan'] ?? 'BELUMMENERIMA'), color: Colors.white, size: 16, ), const SizedBox(width: 4), Text( - penerima['status'] ?? 'Status tidak tersedia', + controller.getStatusText( + warga['status_penerimaan'] ?? 'BELUMMENERIMA'), style: const TextStyle( color: Colors.white, fontSize: 12, @@ -159,7 +158,7 @@ class KonfirmasiPenerimaView extends GetView { ); } - Widget _buildDetailInfo(Map penerima) { + Widget _buildDetailInfo(Map warga) { return Container( padding: const EdgeInsets.all(16), child: Column( @@ -182,21 +181,19 @@ class KonfirmasiPenerimaView extends GetView { padding: const EdgeInsets.all(16), child: Column( children: [ - _buildDetailRow('NIK', penerima['nik'] ?? '-'), - _buildDetailRow('No KK', penerima['noKK'] ?? '-'), + _buildDetailRow('NIK', warga['nik'] ?? '-'), + _buildDetailRow('No KK', warga['no_kk'] ?? '-'), + _buildDetailRow('No Handphone', warga['no_hp'] ?? '-'), + _buildDetailRow('Email', warga['email'] ?? '-'), _buildDetailRow( - 'No Handphone', penerima['noHandphone'] ?? '-'), - _buildDetailRow('Email', penerima['email'] ?? '-'), - _buildDetailRow( - 'Jenis Kelamin', penerima['jenisKelamin'] ?? '-'), - _buildDetailRow('Agama', penerima['agama'] ?? '-'), + 'Jenis Kelamin', warga['jenis_kelamin'] ?? '-'), + _buildDetailRow('Agama', warga['agama'] ?? '-'), _buildDetailRow('Tempat, Tanggal Lahir', - penerima['tempatTanggalLahir'] ?? '-'), + '${warga['tempat_lahir'] ?? '-'}, ${warga['tanggal_lahir'] ?? '-'}'), + _buildDetailRow('Alamat Lengkap', warga['alamat'] ?? '-'), + _buildDetailRow('Pekerjaan', warga['pekerjaan'] ?? '-'), _buildDetailRow( - 'Alamat Lengkap', penerima['alamatLengkap'] ?? '-'), - _buildDetailRow('Pekerjaan', penerima['pekerjaan'] ?? '-'), - _buildDetailRow('Pendidikan Terakhir', - penerima['pendidikanTerakhir'] ?? '-'), + 'Pendidikan Terakhir', warga['pendidikan'] ?? '-'), ], ), ), @@ -206,17 +203,8 @@ class KonfirmasiPenerimaView extends GetView { ); } - Widget _buildDetailJadwalBantuan(Map penerima) { - // Simulasi data jadwal dan bantuan - final jadwalBantuan = { - 'tanggal': '15 Agustus 2023', - 'waktu': '09:00 - 12:00 WIB', - 'lokasi': 'Balai Desa Gunung Putri, Jl. Raya Gunung Putri No. 10', - 'jenisBantuan': 'Bantuan Sosial Tunai (BST)', - 'nilaiNominal': 'Rp 600.000', - 'keterangan': 'Bantuan diberikan dalam bentuk tunai' - }; - + Widget _buildDetailJadwalBantuan( + Map jadwal, dynamic jumlahBantuan) { return Container( padding: const EdgeInsets.all(16), child: Column( @@ -240,15 +228,13 @@ class KonfirmasiPenerimaView extends GetView { child: Column( children: [ _buildDetailRow( - 'Tanggal Penyaluran', jadwalBantuan['tanggal'] ?? '-'), - _buildDetailRow('Waktu', jadwalBantuan['waktu'] ?? '-'), - _buildDetailRow('Lokasi', jadwalBantuan['lokasi'] ?? '-'), + 'Tanggal Penyaluran', jadwal['tanggal'] ?? '-'), + _buildDetailRow('Waktu', jadwal['waktu'] ?? '-'), + _buildDetailRow('Lokasi', jadwal['lokasi'] ?? '-'), _buildDetailRow( - 'Jenis Bantuan', jadwalBantuan['jenisBantuan'] ?? '-'), - _buildDetailRow( - 'Nilai Nominal', jadwalBantuan['nilaiNominal'] ?? '-'), - _buildDetailRow( - 'Keterangan', jadwalBantuan['keterangan'] ?? '-'), + 'Jenis Bantuan', jadwal['jenis_bantuan'] ?? '-'), + _buildDetailRow('Jumlah Bantuan', '$jumlahBantuan item'), + _buildDetailRow('Keterangan', jadwal['keterangan'] ?? '-'), ], ), ), @@ -289,7 +275,7 @@ class KonfirmasiPenerimaView extends GetView { } Widget _buildKonfirmasiForm( - BuildContext context, Map penerima) { + BuildContext context, int penerimaId, String penyaluranId) { return Container( padding: const EdgeInsets.all(16), child: Column( @@ -470,11 +456,23 @@ class KonfirmasiPenerimaView extends GetView { children: [ ClipRRect( borderRadius: BorderRadius.circular(8), - child: Image.asset( + child: Image.network( controller.fotoBuktiPath.value, height: 200, width: double.infinity, fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 200, + width: double.infinity, + color: Colors.grey.shade200, + child: const Icon( + Icons.broken_image, + size: 40, + color: Colors.grey, + ), + ); + }, ), ), Positioned( @@ -545,11 +543,23 @@ class KonfirmasiPenerimaView extends GetView { children: [ ClipRRect( borderRadius: BorderRadius.circular(8), - child: Image.asset( + child: Image.network( controller.tandaTanganPath.value, height: 150, width: double.infinity, fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 150, + width: double.infinity, + color: Colors.grey.shade200, + child: const Icon( + Icons.broken_image, + size: 40, + color: Colors.grey, + ), + ); + }, ), ), Positioned( @@ -606,7 +616,10 @@ class KonfirmasiPenerimaView extends GetView { ); } - Widget _buildBottomButtons(Map penerima) { + Widget _buildBottomButtons( + int penerimaId, String penyaluranId, String statusPenerimaan) { + final bool sudahDiterima = statusPenerimaan == 'SUDAHMENERIMA'; + return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -631,18 +644,22 @@ class KonfirmasiPenerimaView extends GetView { const SizedBox(width: 16), Expanded( child: Obx(() => ElevatedButton( - onPressed: controller.isKonfirmasiChecked.value && - controller.isIdentitasChecked.value && - controller.isDataValidChecked.value && - controller.fotoBuktiPath.value.isNotEmpty && - controller.tandaTanganPath.value.isNotEmpty - ? () => controller.konfirmasiPenyaluran(penerima['id']) - : null, + onPressed: sudahDiterima + ? null + : (controller.isKonfirmasiChecked.value && + controller.isIdentitasChecked.value && + controller.isDataValidChecked.value && + controller.fotoBuktiPath.value.isNotEmpty && + controller.tandaTanganPath.value.isNotEmpty + ? () => controller.konfirmasiPenyaluran( + penerimaId, penyaluranId) + : null), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.primaryColor, disabledBackgroundColor: Colors.grey.shade300, ), - child: const Text('Konfirmasi'), + child: + Text(sudahDiterima ? 'Sudah Dikonfirmasi' : 'Konfirmasi'), )), ), ], diff --git a/lib/app/modules/petugas_desa/views/pelaksanaan_penyaluran_view.dart b/lib/app/modules/petugas_desa/views/pelaksanaan_penyaluran_view.dart deleted file mode 100644 index 453c25c..0000000 --- a/lib/app/modules/petugas_desa/views/pelaksanaan_penyaluran_view.dart +++ /dev/null @@ -1,878 +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 PelaksanaanPenyaluranView extends GetView { - const PelaksanaanPenyaluranView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - // Ambil data jadwal dari parameter - final jadwal = Get.arguments as Map; - - // Debug: Tampilkan data jadwal yang diterima - print('DEBUG: Jadwal yang diterima: $jadwal'); - print('DEBUG: ID Jadwal: ${jadwal['id']}'); - - // Debug: Periksa koneksi ke Supabase menggunakan instance dari controller - try { - controller.supabaseService.client - .from('penyaluran_bantuan') - .select('id') - .limit(1) - .then((_) { - print('DEBUG: Koneksi ke Supabase berhasil'); - }).catchError((error) { - print('DEBUG: Koneksi ke Supabase gagal: $error'); - }); - } catch (e) { - print('DEBUG: Error saat memeriksa koneksi Supabase: $e'); - } - - // Debug: Periksa struktur data jadwal - controller.debugJadwalData(jadwal); - - // Muat data penerima saat halaman dimuat - WidgetsBinding.instance.addPostFrameCallback((_) { - controller.reloadPenerimaPenyaluran(); - }); - - return Scaffold( - appBar: AppBar( - title: const Text('Pelaksanaan Penyaluran'), - // actions: [ - // // Tombol debug untuk melihat SQL query - // IconButton( - // icon: const Icon(Icons.code), - // onPressed: () { - // final penyaluranId = Get.parameters['id'] ?? jadwal['id']; - // _showSqlDebugDialog(context, penyaluranId); - // }, - // tooltip: 'Lihat SQL Query', - // ), - // ], - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Informasi jadwal - Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withAlpha(26), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeaderInfo(context, jadwal), - ], - ), - ), - - // Daftar penerima bantuan - _buildDaftarPenerima(context, jadwal), - ], - ), - ), - bottomNavigationBar: _buildBottomButtons(context, jadwal), - ); - } - - Widget _buildHeaderInfo(BuildContext context, Map jadwal) { - final textTheme = Theme.of(context).textTheme; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - jadwal['lokasi'] ?? 'Lokasi Penyaluran', - style: textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - _buildInfoItem( - context, - icon: Icons.category, - label: 'Jenis Bantuan', - value: jadwal['jenis_bantuan'] ?? 'Tidak tersedia', - ), - _buildInfoItem( - context, - icon: Icons.calendar_today, - label: 'Tanggal', - value: jadwal['tanggal'] ?? 'Tidak tersedia', - ), - _buildInfoItem( - context, - icon: Icons.access_time, - label: 'Waktu', - value: jadwal['waktu'] ?? 'Tidak tersedia', - ), - _buildInfoItem( - context, - icon: Icons.people, - label: 'Jumlah Penerima', - value: '${controller.jumlahPenerima} orang', - ), - ], - ); - } - - Widget _buildInfoItem( - BuildContext context, { - required IconData icon, - required String label, - required String value, - bool isStatus = false, - }) { - final bool isActive = isStatus && value.toUpperCase() == 'AKTIF'; - - return Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Row( - children: [ - Icon( - icon, - size: 18, - color: Colors.grey[600], - ), - const SizedBox(width: 8), - Text( - '$label: ', - style: const TextStyle( - fontWeight: FontWeight.w500, - ), - ), - if (isStatus) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: isActive ? Colors.green[50] : Colors.orange[50], - borderRadius: BorderRadius.circular(12), - ), - child: Text( - value, - style: TextStyle( - color: isActive ? Colors.green : Colors.orange, - fontWeight: FontWeight.w500, - ), - ), - ) - else - Expanded( - child: Text( - value, - style: const TextStyle( - fontWeight: FontWeight.w500, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ); - } - - Widget _buildDaftarPenerima( - BuildContext context, Map jadwal) { - // Debug: Periksa validitas ID penyaluran - final penyaluranId = jadwal['id']; - if (penyaluranId == null || penyaluranId.toString().isEmpty) { - print('DEBUG: PERINGATAN! ID penyaluran kosong atau null: $penyaluranId'); - - // Tampilkan pesan error jika ID tidak valid - return Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Column( - children: [ - const Icon(Icons.error_outline, color: Colors.red, size: 48), - const SizedBox(height: 16), - Text( - 'ID penyaluran tidak valid: $penyaluranId', - style: TextStyle(color: Colors.red), - textAlign: TextAlign.center, - ), - ], - ), - ), - ); - } - - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Daftar Penerima Bantuan', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - Obx(() => Text( - '${controller.jumlahPenerima.value} orang', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.grey.shade600, - ), - )), - ], - ), - const SizedBox(height: 16), - - // Search bar - TextField( - controller: controller.searchPenerimaController, - onChanged: (value) => controller.filterPenerima(value), - decoration: InputDecoration( - hintText: 'Cari penerima...', - prefixIcon: const Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey.shade300), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey.shade300), - ), - contentPadding: const EdgeInsets.symmetric(vertical: 12), - filled: true, - fillColor: Colors.grey.shade50, - ), - ), - - const SizedBox(height: 16), - - // Filter status - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - _buildFilterChip( - context, 'Semua', controller.filterStatus.value == 'SEMUA'), - const SizedBox(width: 8), - _buildFilterChip(context, 'Sudah Diterima', - controller.filterStatus.value == 'SUDAHMENERIMA'), - const SizedBox(width: 8), - _buildFilterChip(context, 'Belum Diterima', - controller.filterStatus.value == 'BELUMMENERIMA'), - ], - ), - ), - - const SizedBox(height: 16), - - // Daftar penerima - gunakan SizedBox dengan height tertentu daripada Expanded - SizedBox( - height: 400, // Tinggi tetap, sesuaikan sesuai kebutuhan - child: Obx(() { - // Tampilkan loading jika sedang memuat ulang data - if (controller.isLoading.value) { - return const Center( - child: CircularProgressIndicator(), - ); - } - - // Tampilkan pesan jika tidak ada data - if (controller.filteredPenerimaPenyaluran.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.people_outline, - color: Colors.grey, - size: 60, - ), - const SizedBox(height: 16), - const Text( - 'Tidak ada data penerima untuk penyaluran ini', - style: TextStyle(fontSize: 16), - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () { - controller.reloadPenerimaPenyaluran(); - }, - child: const Text('Refresh Data'), - ), - ], - ), - ); - } - - // Tampilkan data penerima - return ListView.builder( - itemCount: controller.filteredPenerimaPenyaluran.length, - itemBuilder: (context, index) { - final penerima = controller.filteredPenerimaPenyaluran[index]; - return _buildPenerimaItem(context, penerima); - }, - ); - }), - ), - ], - ), - ); - } - - Widget _buildFilterChip(BuildContext context, String label, bool isSelected) { - String statusValue; - switch (label) { - case 'Sudah Diterima': - statusValue = 'SUDAHMENERIMA'; - break; - case 'Belum Diterima': - statusValue = 'BELUMMENERIMA'; - break; - default: - statusValue = 'SEMUA'; - } - - return FilterChip( - label: Text(label), - selected: isSelected, - onSelected: (selected) { - if (selected) { - controller.filterStatus.value = statusValue; - controller.applyFilters(); - } - }, - backgroundColor: Colors.grey.shade100, - selectedColor: AppTheme.primaryColor.withOpacity(0.2), - checkmarkColor: AppTheme.primaryColor, - labelStyle: TextStyle( - color: isSelected ? AppTheme.primaryColor : Colors.black, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - ), - ); - } - - // Metode untuk menampilkan dialog debug - void _showDebugDialog(BuildContext context, Map data) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Debug Data'), - content: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Data Struktur:', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - Text('Keys: ${data.keys.toList().join(', ')}'), - const Divider(), - if (data.containsKey('warga')) ...[ - const Text('Warga Data:', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - if (data['warga'] != null) - Text( - 'Warga Keys: ${(data['warga'] as Map).keys.toList().join(', ')}') - else - const Text('Warga data is null'), - const Divider(), - ], - const Text('Raw Data:', - style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - Text(data.toString(), style: const TextStyle(fontSize: 12)), - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: const Text('Tutup'), - ), - ], - ), - ); - } - - // Metode untuk membangun item penerima dengan tombol debug - Widget _buildPenerimaItem( - BuildContext context, Map penerima) { - final bool sudahDiterima = penerima['status_penerimaan'] == 'SUDAHMENERIMA'; - final warga = penerima['warga'] as Map?; - - return Container( - 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: ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - title: Text( - warga?['nama_lengkap'] ?? 'Nama tidak tersedia', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text('NIK: ${warga?['nik'] ?? 'NIK tidak tersedia'}'), - const SizedBox(height: 2), - Text('Alamat: ${warga?['alamat'] ?? 'Alamat tidak tersedia'}'), - if (penerima['jumlah_bantuan'] != null) ...[ - const SizedBox(height: 2), - Text('Jumlah Bantuan: ${penerima['jumlah_bantuan']}'), - ], - ], - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // Tombol debug untuk melihat struktur data - // IconButton( - // icon: const Icon(Icons.bug_report, color: Colors.grey), - // onPressed: () => _showDebugDialog(context, penerima), - // tooltip: 'Lihat struktur data', - // iconSize: 20, - // ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: sudahDiterima - ? Colors.green.withAlpha(26) - : Colors.orange.withAlpha(26), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - sudahDiterima ? 'Sudah Diterima' : 'Belum Diterima', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: sudahDiterima ? Colors.green : Colors.orange, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - onTap: () { - // Navigasi ke halaman konfirmasi penerima - Get.toNamed( - '/konfirmasi-penerima', - arguments: { - 'penerima_id': penerima['id'], - 'penyaluran_id': penerima['penyaluran_bantuan_id'], - 'warga': warga, - 'status_penerimaan': penerima['status_penerimaan'], - 'jumlah_bantuan': penerima['jumlah_bantuan'], - }, - ); - }, - ), - ); - } - - Widget _buildBottomButtons( - BuildContext context, Map jadwal) { - final String status = (jadwal['status'] ?? '').toUpperCase(); - - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withAlpha(50), - spreadRadius: 1, - blurRadius: 5, - offset: const Offset(0, -1), - ), - ], - ), - child: Row( - children: [ - // Tampilkan tombol berdasarkan status - if (status == 'AKTIF') ...[ - // Tombol Cetak Laporan - Expanded( - child: ElevatedButton.icon( - onPressed: () { - Get.snackbar( - 'Informasi', - 'Mencetak laporan penyaluran...', - snackPosition: SnackPosition.TOP, - ); - }, - icon: const Icon(Icons.print), - label: const Text('Cetak Laporan'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - const SizedBox(width: 12), - // Tombol Selesaikan - Expanded( - child: ElevatedButton.icon( - onPressed: () { - _showSelesaikanDialog(context, jadwal); - }, - icon: const Icon(Icons.check_circle), - label: const Text('Selesaikan'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - ] else if (status == 'SELESAI') ...[ - // Hanya tampilkan tombol Cetak Laporan - Expanded( - child: ElevatedButton.icon( - onPressed: () { - Get.snackbar( - 'Informasi', - 'Mencetak laporan penyaluran...', - snackPosition: SnackPosition.TOP, - ); - }, - icon: const Icon(Icons.print), - label: const Text('Cetak Laporan'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - ] else if (status == 'DIBATALKAN') ...[ - // Tampilkan pesan dibatalkan - Expanded( - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.red[50], - borderRadius: BorderRadius.circular(8), - ), - child: const Text( - 'Penyaluran Dibatalkan', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ] else ...[ - // Status lainnya - tampilkan pesan default - Expanded( - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), - ), - child: Text( - 'Status: $status', - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.grey, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ], - ), - ); - } - - void _showSelesaikanDialog( - BuildContext context, Map jadwal) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Selesaikan Penyaluran'), - content: - const Text('Apakah Anda yakin ingin menyelesaikan penyaluran ini? ' - 'Pastikan semua penerima telah dikonfirmasi.'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Batal'), - ), - ElevatedButton( - onPressed: () { - // Implementasi selesaikan penyaluran - controller.completeJadwal(jadwal['id']).then((_) { - Navigator.pop(context); - Get.back(); // Kembali ke halaman sebelumnya - Get.snackbar( - 'Berhasil', - 'Penyaluran telah diselesaikan', - snackPosition: SnackPosition.TOP, - backgroundColor: Colors.green, - colorText: Colors.white, - ); - }).catchError((error) { - Navigator.pop(context); - Get.snackbar( - 'Gagal', - 'Terjadi kesalahan: $error', - snackPosition: SnackPosition.TOP, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - }); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - ), - child: const Text('Selesaikan'), - ), - ], - ), - ); - } - - // Metode untuk menampilkan filter dan pencarian - Widget _buildFilterAndSearch(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withAlpha(26), - spreadRadius: 1, - blurRadius: 3, - offset: const Offset(0, 1), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Filter & Pencarian', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - // Filter status - Row( - children: [ - const Text('Status: '), - const SizedBox(width: 8), - Expanded( - child: Obx(() { - final currentFilter = controller.filterStatus.value; - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - // Filter Semua - InkWell( - onTap: () => controller.filterStatus.value = 'SEMUA', - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: currentFilter == 'SEMUA' - ? Colors.blue - : Colors.grey[200], - borderRadius: BorderRadius.circular(16), - ), - child: Text( - 'Semua', - style: TextStyle( - color: currentFilter == 'SEMUA' - ? Colors.white - : Colors.black87, - fontWeight: currentFilter == 'SEMUA' - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ), - ), - const SizedBox(width: 8), - // Filter Sudah Menerima - InkWell( - onTap: () => - controller.filterStatus.value = 'SUDAHMENERIMA', - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: currentFilter == 'SUDAHMENERIMA' - ? Colors.blue - : Colors.grey[200], - borderRadius: BorderRadius.circular(16), - ), - child: Text( - 'Sudah Menerima', - style: TextStyle( - color: currentFilter == 'SUDAHMENERIMA' - ? Colors.white - : Colors.black87, - fontWeight: currentFilter == 'SUDAHMENERIMA' - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ), - ), - const SizedBox(width: 8), - // Filter Belum Menerima - InkWell( - onTap: () => - controller.filterStatus.value = 'BELUMMENERIMA', - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: currentFilter == 'BELUMMENERIMA' - ? Colors.blue - : Colors.grey[200], - borderRadius: BorderRadius.circular(16), - ), - child: Text( - 'Belum Menerima', - style: TextStyle( - color: currentFilter == 'BELUMMENERIMA' - ? Colors.white - : Colors.black87, - fontWeight: currentFilter == 'BELUMMENERIMA' - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ), - ), - ], - ), - ); - }), - ), - ], - ), - const SizedBox(height: 16), - // Pencarian - TextField( - onChanged: (value) => controller.searchQuery.value = value, - decoration: InputDecoration( - hintText: 'Cari berdasarkan nama atau NIK', - prefixIcon: const Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - contentPadding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - ), - ), - ], - ), - ); - } - - // Metode untuk menampilkan dialog debug SQL - void _showSqlDebugDialog(BuildContext context, String penyaluranId) { - final validId = controller.ensureValidUUID(penyaluranId); - final sqlQuery = ''' -SELECT - penerima_penyaluran.*, - warga.* -FROM - penerima_penyaluran -LEFT JOIN - warga ON warga.id = penerima_penyaluran.warga_id -WHERE - penerima_penyaluran.penyaluran_bantuan_id = '$validId'; -'''; - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('SQL Query Debug'), - content: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('SQL Query yang digunakan:'), - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(8), - ), - child: SelectableText( - sqlQuery, - style: const TextStyle( - fontFamily: 'monospace', - fontSize: 12, - ), - ), - ), - const SizedBox(height: 16), - const Text('Petunjuk:'), - const SizedBox(height: 8), - const Text('1. Salin query ini ke SQL Editor di Supabase'), - const Text('2. Jalankan query untuk melihat hasil'), - const Text( - '3. Bandingkan dengan data yang ditampilkan di aplikasi'), - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: const Text('Tutup'), - ), - ], - ), - ); - } -} 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 51fbfbc..9be5f87 100644 --- a/lib/app/modules/petugas_desa/views/petugas_desa_view.dart +++ b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart @@ -378,10 +378,6 @@ class PetugasDesaView extends GetView { } Widget _buildBottomNavigationBar() { - // Tambahkan print statement untuk debugging - print('Jumlah pengaduan diproses: ${controller.jumlahDiproses.value}'); - print('Jumlah jadwal hari ini: ${controller.jadwalHariIni.length}'); - return Obx(() { return BottomNavigationBar( currentIndex: controller.activeTabIndex.value, diff --git a/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart b/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart index 6a2e002..76c1823 100644 --- a/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart +++ b/lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart @@ -221,79 +221,75 @@ class TambahPenyaluranView extends GetView { selectedSkemaBantuanId.value ?? '') .eq('status', 'TERVERIFIKASI'); - if (pengajuanData != null) { - Get.dialog( - Dialog( - child: Container( - width: MediaQuery.of(context).size.width * 0.9, - height: - MediaQuery.of(context).size.height * 0.8, - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Daftar Penerima Bantuan', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + Get.dialog( + Dialog( + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + height: MediaQuery.of(context).size.height * 0.8, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Daftar Penerima Bantuan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), - IconButton( - onPressed: () => Get.back(), - icon: const Icon(Icons.close), - ), - ], - ), - const SizedBox(height: 16), - Expanded( + ), + IconButton( + onPressed: () => Get.back(), + icon: const Icon(Icons.close), + ), + ], + ), + const SizedBox(height: 16), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( - child: DataTable( - columnSpacing: 20, - horizontalMargin: 20, - columns: const [ - DataColumn(label: Text('No')), - DataColumn(label: Text('Nama')), - DataColumn(label: Text('NIK')), - DataColumn(label: Text('Alamat')), - ], - rows: pengajuanData - .asMap() - .entries - .map((entry) { - final warga = - entry.value['warga']; - return DataRow( - cells: [ - DataCell( - Text('${entry.key + 1}')), - DataCell(Text( - warga['nama_lengkap'] ?? - '-')), - DataCell(Text( - warga['nik'] ?? '-')), - DataCell(Text( - warga['alamat'] ?? '-')), - ], - ); - }).toList(), - ), + child: DataTable( + columnSpacing: 20, + horizontalMargin: 20, + columns: const [ + DataColumn(label: Text('No')), + DataColumn(label: Text('Nama')), + DataColumn(label: Text('NIK')), + DataColumn(label: Text('Alamat')), + ], + rows: pengajuanData + .asMap() + .entries + .map((entry) { + final warga = entry.value['warga']; + return DataRow( + cells: [ + DataCell( + Text('${entry.key + 1}')), + DataCell(Text( + warga['nama_lengkap'] ?? + '-')), + DataCell( + Text(warga['nik'] ?? '-')), + DataCell(Text( + warga['alamat'] ?? '-')), + ], + ); + }).toList(), ), ), ), - ], - ), + ), + ], ), ), - ); - } + ), + ); }, icon: const Icon(Icons.people), label: const Text('Lihat Daftar Penerima'), diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index f41eba7..c75247f 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -7,7 +7,6 @@ import 'package:penyaluran_app/app/modules/petugas_desa/views/permintaan_penjadw import 'package:penyaluran_app/app/modules/petugas_desa/views/daftar_penerima_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_penerima_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/konfirmasi_penerima_view.dart'; -import 'package:penyaluran_app/app/modules/petugas_desa/views/pelaksanaan_penyaluran_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/riwayat_penitipan_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/daftar_donatur_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_donatur_view.dart'; @@ -63,11 +62,6 @@ class AppPages { page: () => const KonfirmasiPenerimaView(), binding: PenerimaBinding(), ), - GetPage( - name: _Paths.pelaksanaanPenyaluran, - page: () => const PelaksanaanPenyaluranView(), - binding: PetugasDesaBinding(), - ), GetPage( name: _Paths.profile, page: () => const ProfileView(), diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index f0f6db5..ed144c1 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -20,6 +20,9 @@ abstract class Routes { static const daftarDonatur = _Paths.daftarDonatur; static const detailDonatur = _Paths.detailDonatur; static const tambahPenyaluran = _Paths.tambahPenyaluran; + static const daftarPenerimaPenyaluran = _Paths.daftarPenerimaPenyaluran; + static const detailPenerimaPenyaluran = _Paths.detailPenerimaPenyaluran; + static const laporanPenyaluran = _Paths.laporanPenyaluran; } abstract class _Paths { @@ -42,4 +45,7 @@ abstract class _Paths { static const daftarDonatur = '/daftar-donatur'; static const detailDonatur = '/daftar-donatur/detail'; static const tambahPenyaluran = '/tambah-penyaluran'; + static const daftarPenerimaPenyaluran = '/daftar-penerima-penyaluran'; + static const detailPenerimaPenyaluran = '/detail-penerima-penyaluran'; + static const laporanPenyaluran = '/laporan-penyaluran'; } diff --git a/lib/app/services/supabase_service.dart b/lib/app/services/supabase_service.dart index fcea23f..4e477b7 100644 --- a/lib/app/services/supabase_service.dart +++ b/lib/app/services/supabase_service.dart @@ -1,7 +1,6 @@ import 'package:get/get.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'dart:io'; -import 'package:penyaluran_app/app/utils/date_time_helper.dart'; class SupabaseService extends GetxService { static SupabaseService get to => Get.find();