Perbarui JadwalSectionWidget dan KonfirmasiPenerimaView untuk menyederhanakan pengiriman data. Hapus konversi model ke Map dan hanya kirim ID penyaluran. Modifikasi tampilan KonfirmasiPenerimaView untuk menggunakan data dari arguments dan memperbarui logika tampilan penerima. Hapus tampilan PelaksanaanPenyaluranView yang tidak digunakan dan perbarui rute aplikasi untuk mencerminkan perubahan ini.

This commit is contained in:
Khafidh Fuadi
2025-03-15 15:13:41 +07:00
parent 675b0a7cad
commit 295b76e40f
10 changed files with 178 additions and 1072 deletions

View File

@ -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>(
() => PelaksanaanPenyaluranController(),
);
}
}

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/data/models/penyaluran_bantuan_model.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart'; import 'package:penyaluran_app/app/routes/app_pages.dart';
@ -204,24 +203,8 @@ class JadwalSectionWidget extends StatelessWidget {
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
onTap: () { onTap: () {
// Konversi PenyaluranBantuanModel ke Map<String, dynamic> // Hanya kirim ID penyaluran
final jadwalMap = { Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwal.id);
'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);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -336,25 +319,9 @@ class JadwalSectionWidget extends StatelessWidget {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: TextButton.icon( child: TextButton.icon(
onPressed: () { onPressed: () {
// Konversi PenyaluranBantuanModel ke Map<String, dynamic> // Hanya kirim ID penyaluran
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, Get.toNamed(Routes.pelaksanaanPenyaluran,
arguments: jadwalMap); arguments: jadwal.id);
}, },
icon: const Icon(Icons.info_outline, size: 16), icon: const Icon(Icons.info_outline, size: 16),
label: const Text('Lihat Detail'), label: const Text('Lihat Detail'),

View File

@ -219,8 +219,6 @@ class PetugasDesaController extends GetxController {
// Update counter // Update counter
_counterService.updatePengaduanCounter(diproses); _counterService.updatePengaduanCounter(diproses);
print('Jumlah pengaduan diproses: $diproses');
} else { } else {
_counterService.updatePengaduanCounter(0); _counterService.updatePengaduanCounter(0);
} }

View File

@ -1,14 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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'; import 'package:penyaluran_app/app/theme/app_theme.dart';
class KonfirmasiPenerimaView extends GetView<PenerimaController> { class KonfirmasiPenerimaView extends GetView<PelaksanaanPenyaluranController> {
const KonfirmasiPenerimaView({super.key}); const KonfirmasiPenerimaView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String id = Get.arguments as String; // Ambil data dari arguments
final Map<String, dynamic> args = Get.arguments ?? {};
// Pastikan semua parameter yang diperlukan tersedia
final penerimaId = args['penerima_id'] ?? 0;
final String penyaluranId = args['penyaluran_id']?.toString() ?? '';
final Map<String, dynamic> warga =
args['warga'] as Map<String, dynamic>? ?? {};
final Map<String, dynamic> jadwal =
args['jadwal'] as Map<String, dynamic>? ?? {};
final String statusPenerimaan =
args['status_penerimaan']?.toString() ?? 'BELUMMENERIMA';
final dynamic jumlahBantuan = args['jumlah_bantuan'] ?? 1;
return Obx(() { return Obx(() {
if (controller.isLoading.value) { if (controller.isLoading.value) {
@ -22,19 +34,6 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
); );
} }
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( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Konfirmasi Penerima'), title: const Text('Konfirmasi Penerima'),
@ -47,27 +46,28 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
child: Column( child: Column(
children: [ children: [
// Header dengan foto dan nama // Header dengan foto dan nama
_buildHeader(penerima), _buildHeader(warga),
// Detail informasi penerima // Detail informasi penerima
_buildDetailInfo(penerima), _buildDetailInfo(warga),
// Detail jadwal dan bantuan // Detail jadwal dan bantuan
_buildDetailJadwalBantuan(penerima), _buildDetailJadwalBantuan(jadwal, jumlahBantuan),
// Form konfirmasi // Form konfirmasi
_buildKonfirmasiForm(context, penerima), _buildKonfirmasiForm(context, penerimaId, penyaluranId),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
), ),
), ),
bottomNavigationBar: _buildBottomButtons(penerima), bottomNavigationBar:
_buildBottomButtons(penerimaId, penyaluranId, statusPenerimaan),
); );
}); });
} }
Widget _buildHeader(Map<String, dynamic> penerima) { Widget _buildHeader(Map<String, dynamic> warga) {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -80,11 +80,11 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
CircleAvatar( CircleAvatar(
radius: 40, radius: 40,
backgroundColor: Colors.white, backgroundColor: Colors.white,
child: penerima['foto'] != null child: warga['foto_url'] != null
? ClipRRect( ? ClipRRect(
borderRadius: BorderRadius.circular(40), borderRadius: BorderRadius.circular(40),
child: Image.asset( child: Image.network(
penerima['foto'], warga['foto_url'],
width: 80, width: 80,
height: 80, height: 80,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -106,7 +106,7 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
const SizedBox(height: 12), const SizedBox(height: 12),
// Nama penerima // Nama penerima
Text( Text(
penerima['nama'] ?? 'Nama tidak tersedia', warga['nama'] ?? 'Nama tidak tersedia',
style: const TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -116,7 +116,7 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
const SizedBox(height: 4), const SizedBox(height: 4),
// NIK // NIK
Text( Text(
penerima['nik'] ?? 'NIK tidak tersedia', warga['nik'] ?? 'NIK tidak tersedia',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.white, color: Colors.white,
@ -127,24 +127,23 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: penerima['status'] == 'Terjadwal' color: controller.getStatusColor(
? AppTheme.scheduledColor warga['status_penerimaan'] ?? 'BELUMMENERIMA'),
: AppTheme.completedColor,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( Icon(
penerima['status'] == 'Terjadwal' controller.getStatusIcon(
? Icons.event_available warga['status_penerimaan'] ?? 'BELUMMENERIMA'),
: Icons.check_circle,
color: Colors.white, color: Colors.white,
size: 16, size: 16,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
penerima['status'] ?? 'Status tidak tersedia', controller.getStatusText(
warga['status_penerimaan'] ?? 'BELUMMENERIMA'),
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12, fontSize: 12,
@ -159,7 +158,7 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
); );
} }
Widget _buildDetailInfo(Map<String, dynamic> penerima) { Widget _buildDetailInfo(Map<String, dynamic> warga) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@ -182,21 +181,19 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ children: [
_buildDetailRow('NIK', penerima['nik'] ?? '-'), _buildDetailRow('NIK', warga['nik'] ?? '-'),
_buildDetailRow('No KK', penerima['noKK'] ?? '-'), _buildDetailRow('No KK', warga['no_kk'] ?? '-'),
_buildDetailRow('No Handphone', warga['no_hp'] ?? '-'),
_buildDetailRow('Email', warga['email'] ?? '-'),
_buildDetailRow( _buildDetailRow(
'No Handphone', penerima['noHandphone'] ?? '-'), 'Jenis Kelamin', warga['jenis_kelamin'] ?? '-'),
_buildDetailRow('Email', penerima['email'] ?? '-'), _buildDetailRow('Agama', warga['agama'] ?? '-'),
_buildDetailRow(
'Jenis Kelamin', penerima['jenisKelamin'] ?? '-'),
_buildDetailRow('Agama', penerima['agama'] ?? '-'),
_buildDetailRow('Tempat, Tanggal Lahir', _buildDetailRow('Tempat, Tanggal Lahir',
penerima['tempatTanggalLahir'] ?? '-'), '${warga['tempat_lahir'] ?? '-'}, ${warga['tanggal_lahir'] ?? '-'}'),
_buildDetailRow('Alamat Lengkap', warga['alamat'] ?? '-'),
_buildDetailRow('Pekerjaan', warga['pekerjaan'] ?? '-'),
_buildDetailRow( _buildDetailRow(
'Alamat Lengkap', penerima['alamatLengkap'] ?? '-'), 'Pendidikan Terakhir', warga['pendidikan'] ?? '-'),
_buildDetailRow('Pekerjaan', penerima['pekerjaan'] ?? '-'),
_buildDetailRow('Pendidikan Terakhir',
penerima['pendidikanTerakhir'] ?? '-'),
], ],
), ),
), ),
@ -206,17 +203,8 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
); );
} }
Widget _buildDetailJadwalBantuan(Map<String, dynamic> penerima) { Widget _buildDetailJadwalBantuan(
// Simulasi data jadwal dan bantuan Map<String, dynamic> jadwal, dynamic jumlahBantuan) {
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'
};
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@ -240,15 +228,13 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
child: Column( child: Column(
children: [ children: [
_buildDetailRow( _buildDetailRow(
'Tanggal Penyaluran', jadwalBantuan['tanggal'] ?? '-'), 'Tanggal Penyaluran', jadwal['tanggal'] ?? '-'),
_buildDetailRow('Waktu', jadwalBantuan['waktu'] ?? '-'), _buildDetailRow('Waktu', jadwal['waktu'] ?? '-'),
_buildDetailRow('Lokasi', jadwalBantuan['lokasi'] ?? '-'), _buildDetailRow('Lokasi', jadwal['lokasi'] ?? '-'),
_buildDetailRow( _buildDetailRow(
'Jenis Bantuan', jadwalBantuan['jenisBantuan'] ?? '-'), 'Jenis Bantuan', jadwal['jenis_bantuan'] ?? '-'),
_buildDetailRow( _buildDetailRow('Jumlah Bantuan', '$jumlahBantuan item'),
'Nilai Nominal', jadwalBantuan['nilaiNominal'] ?? '-'), _buildDetailRow('Keterangan', jadwal['keterangan'] ?? '-'),
_buildDetailRow(
'Keterangan', jadwalBantuan['keterangan'] ?? '-'),
], ],
), ),
), ),
@ -289,7 +275,7 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
} }
Widget _buildKonfirmasiForm( Widget _buildKonfirmasiForm(
BuildContext context, Map<String, dynamic> penerima) { BuildContext context, int penerimaId, String penyaluranId) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@ -470,11 +456,23 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Image.asset( child: Image.network(
controller.fotoBuktiPath.value, controller.fotoBuktiPath.value,
height: 200, height: 200,
width: double.infinity, width: double.infinity,
fit: BoxFit.cover, 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( Positioned(
@ -545,11 +543,23 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Image.asset( child: Image.network(
controller.tandaTanganPath.value, controller.tandaTanganPath.value,
height: 150, height: 150,
width: double.infinity, width: double.infinity,
fit: BoxFit.contain, 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( Positioned(
@ -606,7 +616,10 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
); );
} }
Widget _buildBottomButtons(Map<String, dynamic> penerima) { Widget _buildBottomButtons(
int penerimaId, String penyaluranId, String statusPenerimaan) {
final bool sudahDiterima = statusPenerimaan == 'SUDAHMENERIMA';
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -631,18 +644,22 @@ class KonfirmasiPenerimaView extends GetView<PenerimaController> {
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Obx(() => ElevatedButton( child: Obx(() => ElevatedButton(
onPressed: controller.isKonfirmasiChecked.value && onPressed: sudahDiterima
? null
: (controller.isKonfirmasiChecked.value &&
controller.isIdentitasChecked.value && controller.isIdentitasChecked.value &&
controller.isDataValidChecked.value && controller.isDataValidChecked.value &&
controller.fotoBuktiPath.value.isNotEmpty && controller.fotoBuktiPath.value.isNotEmpty &&
controller.tandaTanganPath.value.isNotEmpty controller.tandaTanganPath.value.isNotEmpty
? () => controller.konfirmasiPenyaluran(penerima['id']) ? () => controller.konfirmasiPenyaluran(
: null, penerimaId, penyaluranId)
: null),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryColor, backgroundColor: AppTheme.primaryColor,
disabledBackgroundColor: Colors.grey.shade300, disabledBackgroundColor: Colors.grey.shade300,
), ),
child: const Text('Konfirmasi'), child:
Text(sudahDiterima ? 'Sudah Dikonfirmasi' : 'Konfirmasi'),
)), )),
), ),
], ],

View File

@ -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<PetugasDesaController> {
const PelaksanaanPenyaluranView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Ambil data jadwal dari parameter
final jadwal = Get.arguments as Map<String, dynamic>;
// 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic>).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<String, dynamic> penerima) {
final bool sudahDiterima = penerima['status_penerimaan'] == 'SUDAHMENERIMA';
final warga = penerima['warga'] as Map<String, dynamic>?;
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<String, dynamic> 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<String, dynamic> 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'),
),
],
),
);
}
}

View File

@ -378,10 +378,6 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
} }
Widget _buildBottomNavigationBar() { 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 Obx(() {
return BottomNavigationBar( return BottomNavigationBar(
currentIndex: controller.activeTabIndex.value, currentIndex: controller.activeTabIndex.value,

View File

@ -221,13 +221,11 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
selectedSkemaBantuanId.value ?? '') selectedSkemaBantuanId.value ?? '')
.eq('status', 'TERVERIFIKASI'); .eq('status', 'TERVERIFIKASI');
if (pengajuanData != null) {
Get.dialog( Get.dialog(
Dialog( Dialog(
child: Container( child: Container(
width: MediaQuery.of(context).size.width * 0.9, width: MediaQuery.of(context).size.width * 0.9,
height: height: MediaQuery.of(context).size.height * 0.8,
MediaQuery.of(context).size.height * 0.8,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -268,8 +266,7 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
.asMap() .asMap()
.entries .entries
.map((entry) { .map((entry) {
final warga = final warga = entry.value['warga'];
entry.value['warga'];
return DataRow( return DataRow(
cells: [ cells: [
DataCell( DataCell(
@ -277,8 +274,8 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
DataCell(Text( DataCell(Text(
warga['nama_lengkap'] ?? warga['nama_lengkap'] ??
'-')), '-')),
DataCell(Text( DataCell(
warga['nik'] ?? '-')), Text(warga['nik'] ?? '-')),
DataCell(Text( DataCell(Text(
warga['alamat'] ?? '-')), warga['alamat'] ?? '-')),
], ],
@ -293,7 +290,6 @@ class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
), ),
), ),
); );
}
}, },
icon: const Icon(Icons.people), icon: const Icon(Icons.people),
label: const Text('Lihat Daftar Penerima'), label: const Text('Lihat Daftar Penerima'),

View File

@ -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/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/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/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/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/daftar_donatur_view.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_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(), page: () => const KonfirmasiPenerimaView(),
binding: PenerimaBinding(), binding: PenerimaBinding(),
), ),
GetPage(
name: _Paths.pelaksanaanPenyaluran,
page: () => const PelaksanaanPenyaluranView(),
binding: PetugasDesaBinding(),
),
GetPage( GetPage(
name: _Paths.profile, name: _Paths.profile,
page: () => const ProfileView(), page: () => const ProfileView(),

View File

@ -20,6 +20,9 @@ abstract class Routes {
static const daftarDonatur = _Paths.daftarDonatur; static const daftarDonatur = _Paths.daftarDonatur;
static const detailDonatur = _Paths.detailDonatur; static const detailDonatur = _Paths.detailDonatur;
static const tambahPenyaluran = _Paths.tambahPenyaluran; static const tambahPenyaluran = _Paths.tambahPenyaluran;
static const daftarPenerimaPenyaluran = _Paths.daftarPenerimaPenyaluran;
static const detailPenerimaPenyaluran = _Paths.detailPenerimaPenyaluran;
static const laporanPenyaluran = _Paths.laporanPenyaluran;
} }
abstract class _Paths { abstract class _Paths {
@ -42,4 +45,7 @@ abstract class _Paths {
static const daftarDonatur = '/daftar-donatur'; static const daftarDonatur = '/daftar-donatur';
static const detailDonatur = '/daftar-donatur/detail'; static const detailDonatur = '/daftar-donatur/detail';
static const tambahPenyaluran = '/tambah-penyaluran'; static const tambahPenyaluran = '/tambah-penyaluran';
static const daftarPenerimaPenyaluran = '/daftar-penerima-penyaluran';
static const detailPenerimaPenyaluran = '/detail-penerima-penyaluran';
static const laporanPenyaluran = '/laporan-penyaluran';
} }

View File

@ -1,7 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import 'dart:io'; import 'dart:io';
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
class SupabaseService extends GetxService { class SupabaseService extends GetxService {
static SupabaseService get to => Get.find<SupabaseService>(); static SupabaseService get to => Get.find<SupabaseService>();