Refactor stok bantuan model dan kontroller untuk mendukung kategori bantuan

- Ubah model StokBantuanModel dari 'jenis bantuan' menjadi 'kategori bantuan'
- Perbarui metode loadJenisBantuanData() menjadi loadKategoriBantuanData()
- Tambahkan metode baru untuk menghitung stok hampir habis dan segera kadaluarsa
- Update tampilan dan form untuk menggunakan kategori bantuan
- Perbaiki logika navigasi dan binding pada berbagai modul terkait
This commit is contained in:
Khafidh Fuadi
2025-03-11 22:14:07 +07:00
parent cdbd659d63
commit f7397cb9cf
12 changed files with 596 additions and 408 deletions

View File

@ -3,14 +3,11 @@ import 'dart:convert';
class StokBantuanModel {
final String? id;
final String? nama;
final String? bentukBantuanId;
final String? sumberBantuanId;
final String? jenisBantuanId;
final Map<String, dynamic>? jenisBantuan;
final String? kategoriBantuanId;
final Map<String, dynamic>? kategoriBantuan;
final double? jumlah;
final String? satuan;
final String? deskripsi;
final String? status;
final DateTime? tanggalMasuk;
final DateTime? tanggalKadaluarsa;
final DateTime? createdAt;
@ -19,14 +16,11 @@ class StokBantuanModel {
StokBantuanModel({
this.id,
this.nama,
this.bentukBantuanId,
this.sumberBantuanId,
this.jenisBantuanId,
this.jenisBantuan,
this.kategoriBantuanId,
this.kategoriBantuan,
this.jumlah,
this.satuan,
this.deskripsi,
this.status,
this.tanggalMasuk,
this.tanggalKadaluarsa,
this.createdAt,
@ -42,14 +36,11 @@ class StokBantuanModel {
StokBantuanModel(
id: json["id"],
nama: json["nama"],
bentukBantuanId: json["bentuk_bantuan_id"],
sumberBantuanId: json["sumber_bantuan_id"],
jenisBantuanId: json["jenis_bantuan_id"],
jenisBantuan: json["jenis_bantuan"],
kategoriBantuanId: json["kategori_bantuan_id"],
kategoriBantuan: json["kategori_bantuan"],
jumlah: json["jumlah"] != null ? json["jumlah"].toDouble() : 0.0,
satuan: json["satuan"],
deskripsi: json["deskripsi"],
status: json["status"],
tanggalMasuk: json["tanggal_masuk"] != null
? DateTime.parse(json["tanggal_masuk"])
: null,
@ -64,19 +55,24 @@ class StokBantuanModel {
: null,
);
Map<String, dynamic> toJson() => {
"id": id,
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = {
"nama": nama,
"bentuk_bantuan_id": bentukBantuanId,
"sumber_bantuan_id": sumberBantuanId,
"jenis_bantuan_id": jenisBantuanId,
"kategori_bantuan_id": kategoriBantuanId,
"jumlah": jumlah,
"satuan": satuan,
"deskripsi": deskripsi,
"status": status,
"tanggal_masuk": tanggalMasuk?.toIso8601String(),
"tanggal_kadaluarsa": tanggalKadaluarsa?.toIso8601String(),
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
// Tambahkan id hanya jika tidak null
if (id != null) {
data["id"] = id;
}
return data;
}
}

View File

@ -116,9 +116,14 @@ class AuthController extends GetxController {
final targetRoute = _getTargetRouteForRole(role);
print('Target rute: $targetRoute');
if (currentRoute != targetRoute) {
// Jika berada di splash atau login, navigasi ke dashboard
if (currentRoute == Routes.splash || currentRoute == Routes.login) {
print('Navigasi ke rute target berdasarkan role');
navigateBasedOnRole(role);
} else if (currentRoute != targetRoute) {
// Jika berada di rute lain yang tidak sesuai dengan role, navigasi ke dashboard
print('Berada di rute yang tidak sesuai, navigasi ke rute target');
navigateBasedOnRole(role);
} else {
print('Sudah berada di rute yang sesuai, tidak perlu navigasi');
}
@ -334,7 +339,7 @@ class AuthController extends GetxController {
}
}
// Mendapatkan rute target berdasarkan peran
// Mendapatkan rute target berdasarkan role
String _getTargetRouteForRole(String role) {
switch (role) {
case 'WARGA':

View File

@ -17,45 +17,54 @@ class PetugasDesaBinding extends Bindings {
Get.put(AuthController(), permanent: true);
}
// Main controller
Get.lazyPut<PetugasDesaController>(
() => PetugasDesaController(),
fenix: true,
);
// Main controller - gunakan put dengan permanent untuk controller utama
if (!Get.isRegistered<PetugasDesaController>()) {
Get.put(PetugasDesaController(), permanent: true);
} else {
// Jika sudah terdaftar, gunakan find untuk mendapatkan instance yang ada
Get.find<PetugasDesaController>();
}
// Dashboard controller
Get.lazyPut<PetugasDesaDashboardController>(
() => PetugasDesaDashboardController(),
fenix: true,
);
// Jadwal penyaluran controller
Get.lazyPut<JadwalPenyaluranController>(
() => JadwalPenyaluranController(),
fenix: true,
);
// Stok bantuan controller
Get.lazyPut<StokBantuanController>(
() => StokBantuanController(),
fenix: true,
);
// Penitipan bantuan controller
Get.lazyPut<PenitipanBantuanController>(
() => PenitipanBantuanController(),
fenix: true,
);
// Pengaduan controller
Get.lazyPut<PengaduanController>(
() => PengaduanController(),
fenix: true,
);
// Penerima bantuan controller
Get.lazyPut<PenerimaBantuanController>(
() => PenerimaBantuanController(),
fenix: true,
);
// Laporan controller
Get.lazyPut<LaporanController>(
() => LaporanController(),
fenix: true,
);
}
}

View File

@ -148,7 +148,7 @@ class JadwalSectionWidget extends StatelessWidget {
),
const SizedBox(height: 8),
Text(
'Jenis Bantuan: ${jadwalData['jenis_bantuan'] ?? ''}',
'Kategori Bantuan: ${jadwalData['kategori_bantuan'] ?? ''}',
style: textTheme.bodyMedium,
),
const SizedBox(height: 4),

View File

@ -181,7 +181,7 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
'Jenis: ${permintaanData['jenis_bantuan'] ?? ''}',
'Kategori: ${permintaanData['kategori_bantuan'] ?? ''}',
style: textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
),

View File

@ -17,8 +17,8 @@ class StokBantuanController extends GetxController {
final RxDouble stokMasuk = 0.0.obs;
final RxDouble stokKeluar = 0.0.obs;
// Data untuk jenis bantuan
final RxList<Map<String, dynamic>> daftarJenisBantuan =
// Data untuk kategori bantuan
final RxList<Map<String, dynamic>> daftarKategoriBantuan =
<Map<String, dynamic>>[].obs;
// Controller untuk pencarian
@ -31,7 +31,7 @@ class StokBantuanController extends GetxController {
void onInit() {
super.onInit();
loadStokBantuanData();
loadJenisBantuanData();
loadKategoriBantuanData();
// Listener untuk pencarian
searchController.addListener(() {
@ -74,14 +74,14 @@ class StokBantuanController extends GetxController {
}
}
Future<void> loadJenisBantuanData() async {
Future<void> loadKategoriBantuanData() async {
try {
final jenisBantuanData = await _supabaseService.getJenisBantuan();
if (jenisBantuanData != null) {
daftarJenisBantuan.value = jenisBantuanData;
final kategoriBantuanData = await _supabaseService.getKategoriBantuan();
if (kategoriBantuanData != null) {
daftarKategoriBantuan.value = kategoriBantuanData;
}
} catch (e) {
print('Error loading jenis bantuan data: $e');
print('Error loading kategori bantuan data: $e');
}
}
@ -167,7 +167,7 @@ class StokBantuanController extends GetxController {
isLoading.value = true;
try {
await loadStokBantuanData();
await loadJenisBantuanData();
await loadKategoriBantuanData();
} finally {
isLoading.value = false;
}
@ -194,4 +194,18 @@ class StokBantuanController extends GetxController {
.toList();
}
}
// Metode untuk mendapatkan jumlah stok yang hampir habis (stok <= 10)
int getStokHampirHabis() {
return daftarStokBantuan.where((stok) => (stok.jumlah ?? 0) <= 10).length;
}
// Metode untuk mendapatkan jumlah stok yang segera kadaluarsa (dalam 30 hari)
int getStokSegeraKadaluarsa() {
return daftarStokBantuan
.where((stok) =>
stok.tanggalKadaluarsa != null &&
stok.tanggalKadaluarsa!.difference(DateTime.now()).inDays <= 30)
.length;
}
}

View File

@ -54,8 +54,8 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
const SizedBox(height: 16),
_buildInfoItem(context,
icon: Icons.category,
label: 'Jenis Bantuan',
value: jadwal['jenis_bantuan'] ?? '-'),
label: 'Kategori Bantuan',
value: jadwal['kategori_bantuan'] ?? '-'),
const SizedBox(height: 8),
_buildInfoItem(context,
icon: Icons.calendar_today,

View File

@ -169,7 +169,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
{
'id': '1',
'donatur': 'PT Sejahtera Abadi',
'jenis_bantuan': 'Sembako',
'kategori_bantuan': 'Sembako',
'jumlah': '500 kg',
'tanggal_pengajuan': '15 April 2023',
'status': 'Menunggu',
@ -177,7 +177,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
{
'id': '2',
'donatur': 'Yayasan Peduli Sesama',
'jenis_bantuan': 'Pakaian',
'kategori_bantuan': 'Pakaian',
'jumlah': '200 pcs',
'tanggal_pengajuan': '14 April 2023',
'status': 'Terverifikasi',
@ -185,7 +185,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
{
'id': '3',
'donatur': 'Bank BRI',
'jenis_bantuan': 'Beras',
'kategori_bantuan': 'Beras',
'jumlah': '300 kg',
'tanggal_pengajuan': '13 April 2023',
'status': 'Terverifikasi',
@ -193,7 +193,7 @@ class PenitipanView extends GetView<PetugasDesaController> {
{
'id': '4',
'donatur': 'Komunitas Peduli',
'jenis_bantuan': 'Alat Tulis',
'kategori_bantuan': 'Alat Tulis',
'jumlah': '100 set',
'tanggal_pengajuan': '12 April 2023',
'status': 'Ditolak',
@ -304,8 +304,8 @@ class PenitipanView extends GetView<PetugasDesaController> {
child: _buildItemDetail(
context,
icon: Icons.category,
label: 'Jenis Bantuan',
value: item['jenis_bantuan'] ?? '',
label: 'Kategori Bantuan',
value: item['kategori_bantuan'] ?? '',
),
),
Expanded(

View File

@ -23,7 +23,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
_showAddStokDialog(context);
},
backgroundColor: AppTheme.primaryColor,
child: const Icon(Icons.add),
child: const Icon(Icons.add, color: Colors.white),
),
);
}
@ -79,7 +79,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
child: _buildSummaryItem(
context,
icon: Icons.inventory_2_outlined,
title: 'Total Stok',
title: 'Stok Tersedia',
value: DateFormatter.formatNumber(controller.totalStok.value),
),
),
@ -87,7 +87,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
child: _buildSummaryItem(
context,
icon: Icons.input,
title: 'Masuk',
title: 'Total Masuk',
value: DateFormatter.formatNumber(controller.stokMasuk.value),
),
),
@ -95,13 +95,48 @@ class StokBantuanView extends GetView<StokBantuanController> {
child: _buildSummaryItem(
context,
icon: Icons.output,
title: 'Keluar',
title: 'Total Keluar',
value:
DateFormatter.formatNumber(controller.stokKeluar.value),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildSummaryItem(
context,
icon: Icons.warning_amber_rounded,
title: 'Hampir Habis',
value: '${controller.getStokHampirHabis()}',
valueColor: controller.getStokHampirHabis() > 0
? Colors.amber
: Colors.white,
),
),
Expanded(
child: _buildSummaryItem(
context,
icon: Icons.access_time,
title: 'Segera Kadaluarsa',
value: '${controller.getStokSegeraKadaluarsa()}',
valueColor: controller.getStokSegeraKadaluarsa() > 0
? Colors.amber
: Colors.white,
),
),
Expanded(
child: _buildSummaryItem(
context,
icon: Icons.category_outlined,
title: 'Kategori Bantuan',
value: '${controller.daftarKategoriBantuan.length}',
),
),
],
),
],
),
);
@ -112,6 +147,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
required IconData icon,
required String title,
required String value,
Color? valueColor,
}) {
return Column(
children: [
@ -132,7 +168,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
value,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
color: valueColor ?? Colors.white,
),
),
const SizedBox(height: 4),
@ -276,9 +312,10 @@ class StokBantuanView extends GetView<StokBantuanController> {
borderRadius: BorderRadius.circular(8),
),
child: Text(
item.jenisBantuan != null
? (item.jenisBantuan!['nama'] ?? 'Tidak Ada Jenis')
: 'Tidak Ada Jenis',
item.kategoriBantuan != null
? (item.kategoriBantuan!['nama'] ??
'Tidak Ada Kategori')
: 'Tidak Ada Kategori',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppTheme.primaryColor,
fontWeight: FontWeight.bold,
@ -314,7 +351,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
context,
icon: Icons.calendar_today,
label: 'Tanggal Masuk',
value: DateFormatter.formatDate(item.tanggalMasuk),
value: DateFormatter.formatDateTime(item.tanggalMasuk),
),
),
],
@ -335,7 +372,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
context,
icon: Icons.access_time,
label: 'Terakhir Diperbarui',
value: DateFormatter.formatDate(item.updatedAt),
value: DateFormatter.formatDateTime(item.updatedAt),
),
),
],
@ -419,12 +456,15 @@ class StokBantuanView extends GetView<StokBantuanController> {
final satuanController = TextEditingController();
final deskripsiController = TextEditingController();
String? selectedJenisBantuanId;
DateTime? tanggalMasuk = DateTime.now();
// Gunakan StatefulBuilder untuk memperbarui state dialog
DateTime tanggalMasuk = DateTime.now();
DateTime? tanggalKadaluarsa;
showDialog(
context: context,
builder: (context) => AlertDialog(
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: const Text('Tambah Stok Bantuan'),
content: Form(
key: formKey,
@ -448,15 +488,15 @@ class StokBantuanView extends GetView<StokBantuanController> {
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Jenis Bantuan',
labelText: 'Kategori Bantuan',
border: OutlineInputBorder(),
),
value: selectedJenisBantuanId,
hint: const Text('Pilih Jenis Bantuan'),
items: controller.daftarJenisBantuan
.map((jenis) => DropdownMenuItem<String>(
value: jenis['id'],
child: Text(jenis['nama'] ?? ''),
hint: const Text('Pilih Kategori Bantuan'),
items: controller.daftarKategoriBantuan
.map((kategori) => DropdownMenuItem<String>(
value: kategori['id'],
child: Text(kategori['nama'] ?? ''),
))
.toList(),
onChanged: (value) {
@ -464,7 +504,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jenis bantuan harus dipilih';
return 'Kategori bantuan harus dipilih';
}
return null;
},
@ -525,12 +565,14 @@ class StokBantuanView extends GetView<StokBantuanController> {
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: tanggalMasuk ?? DateTime.now(),
initialDate: tanggalMasuk,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalMasuk = picked;
});
}
},
child: InputDecorator(
@ -539,7 +581,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalMasuk),
DateFormatter.formatDateTime(tanggalMasuk),
),
),
),
@ -554,7 +596,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalKadaluarsa = picked;
});
}
},
child: InputDecorator(
@ -584,10 +628,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
jumlah: double.parse(jumlahController.text),
satuan: satuanController.text,
deskripsi: deskripsiController.text,
jenisBantuanId: selectedJenisBantuanId,
kategoriBantuanId: selectedJenisBantuanId,
tanggalMasuk: tanggalMasuk,
tanggalKadaluarsa: tanggalKadaluarsa,
status: 'TERSEDIA',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
@ -599,6 +642,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
),
],
),
),
);
}
@ -609,13 +653,16 @@ class StokBantuanView extends GetView<StokBantuanController> {
TextEditingController(text: stok.jumlah?.toString());
final satuanController = TextEditingController(text: stok.satuan);
final deskripsiController = TextEditingController(text: stok.deskripsi);
String? selectedJenisBantuanId = stok.jenisBantuanId;
String? selectedJenisBantuanId = stok.kategoriBantuanId;
// Gunakan StatefulBuilder untuk memperbarui state dialog
DateTime? tanggalMasuk = stok.tanggalMasuk;
DateTime? tanggalKadaluarsa = stok.tanggalKadaluarsa;
showDialog(
context: context,
builder: (context) => AlertDialog(
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: const Text('Edit Stok Bantuan'),
content: Form(
key: formKey,
@ -639,15 +686,20 @@ class StokBantuanView extends GetView<StokBantuanController> {
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Jenis Bantuan',
labelText: 'Kategori Bantuan',
border: OutlineInputBorder(),
),
value: selectedJenisBantuanId,
hint: const Text('Pilih Jenis Bantuan'),
items: controller.daftarJenisBantuan
.map((jenis) => DropdownMenuItem<String>(
value: jenis['id'],
child: Text(jenis['nama'] ?? ''),
hint: const Text('Pilih Kategori Bantuan'),
isExpanded: true,
items: controller.daftarKategoriBantuan
.map((kategori) => DropdownMenuItem<String>(
value: kategori['id'],
child: Text(
kategori['nama'] ?? '',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
))
.toList(),
onChanged: (value) {
@ -655,7 +707,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Jenis bantuan harus dipilih';
return 'Kategori bantuan harus dipilih';
}
return null;
},
@ -721,7 +773,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalMasuk = picked;
});
}
},
child: InputDecorator(
@ -730,7 +784,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
border: OutlineInputBorder(),
),
child: Text(
DateFormatter.formatDate(tanggalMasuk),
DateFormatter.formatDateTime(tanggalMasuk),
),
),
),
@ -745,7 +799,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
lastDate: DateTime(2030),
);
if (picked != null) {
setState(() {
tanggalKadaluarsa = picked;
});
}
},
child: InputDecorator(
@ -776,10 +832,9 @@ class StokBantuanView extends GetView<StokBantuanController> {
jumlah: double.parse(jumlahController.text),
satuan: satuanController.text,
deskripsi: deskripsiController.text,
jenisBantuanId: selectedJenisBantuanId,
kategoriBantuanId: selectedJenisBantuanId,
tanggalMasuk: tanggalMasuk,
tanggalKadaluarsa: tanggalKadaluarsa,
status: stok.status,
createdAt: stok.createdAt,
updatedAt: DateTime.now(),
);
@ -791,6 +846,7 @@ class StokBantuanView extends GetView<StokBantuanController> {
),
],
),
),
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart';
@ -14,12 +15,19 @@ class _SplashViewState extends State<SplashView> {
@override
void initState() {
super.initState();
_navigateToLogin();
_checkAuthAndNavigate();
}
_navigateToLogin() async {
_checkAuthAndNavigate() async {
// Tunggu 2 detik untuk menampilkan splash screen
await Future.delayed(const Duration(seconds: 2));
Get.offAllNamed(Routes.login);
// Dapatkan AuthController dan periksa status autentikasi
final AuthController authController = Get.find<AuthController>();
await authController.checkAuthStatus();
// Navigasi akan ditangani oleh AuthController
// Tidak perlu navigasi manual di sini
}
@override

View File

@ -9,6 +9,9 @@ class SupabaseService extends GetxService {
// Cache untuk profil pengguna
Map<String, dynamic>? _cachedUserProfile;
// Flag untuk menandai apakah sesi sudah diinisialisasi
bool _isSessionInitialized = false;
// Ganti dengan URL dan API key Supabase Anda
static const String supabaseUrl = String.fromEnvironment('SUPABASE_URL',
defaultValue: 'http://labulabs.net:8000');
@ -17,13 +20,47 @@ class SupabaseService extends GetxService {
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzMxODYyODAwLAogICJleHAiOiAxODg5NjI5MjAwCn0.4IpwhwCVbfYXxb8JlZOLSBzCt6kQmypkvuso7N8Aicc');
Future<SupabaseService> init() async {
try {
await Supabase.initialize(
url: supabaseUrl,
anonKey: supabaseKey,
debug: true, // Aktifkan debug untuk melihat log autentikasi
);
client = Supabase.instance.client;
// Tambahkan listener untuk perubahan autentikasi
client.auth.onAuthStateChange.listen((data) {
final AuthChangeEvent event = data.event;
print('DEBUG: Auth state changed: $event');
if (event == AuthChangeEvent.signedIn) {
print('DEBUG: User signed in');
_isSessionInitialized = true;
} else if (event == AuthChangeEvent.signedOut) {
print('DEBUG: User signed out');
_cachedUserProfile = null;
_isSessionInitialized = false;
} else if (event == AuthChangeEvent.tokenRefreshed) {
print('DEBUG: Token refreshed');
_isSessionInitialized = true;
}
});
// Periksa apakah ada sesi yang aktif
final session = client.auth.currentSession;
if (session != null) {
print('DEBUG: Session aktif ditemukan saat inisialisasi');
_isSessionInitialized = true;
} else {
print('DEBUG: Tidak ada session aktif saat inisialisasi');
}
return this;
} catch (e) {
print('ERROR: Gagal inisialisasi Supabase: $e');
rethrow;
}
}
// Metode untuk mendaftar pengguna baru
@ -37,23 +74,52 @@ class SupabaseService extends GetxService {
// Metode untuk login
Future<AuthResponse> signIn(String email, String password) async {
return await client.auth.signInWithPassword(
final response = await client.auth.signInWithPassword(
email: email,
password: password,
);
if (response.user != null) {
_isSessionInitialized = true;
print('DEBUG: Login berhasil, sesi diinisialisasi');
}
return response;
}
// Metode untuk logout
Future<void> signOut() async {
_cachedUserProfile = null; // Hapus cache saat logout
_isSessionInitialized = false;
await client.auth.signOut();
print('DEBUG: Logout berhasil, sesi dihapus');
}
// Metode untuk mendapatkan user saat ini
User? get currentUser => client.auth.currentUser;
// Metode untuk memeriksa apakah user sudah login
bool get isAuthenticated => currentUser != null;
bool get isAuthenticated {
final user = currentUser;
final session = client.auth.currentSession;
if (user != null && session != null) {
// Periksa apakah token masih valid
final now = DateTime.now().millisecondsSinceEpoch / 1000;
final isValid = session.expiresAt != null && session.expiresAt! > now;
if (isValid) {
print('DEBUG: Sesi valid, user terautentikasi');
return true;
} else {
print('DEBUG: Sesi kedaluwarsa, user tidak terautentikasi');
return false;
}
}
print('DEBUG: Tidak ada user atau sesi, user tidak terautentikasi');
return false;
}
// Metode untuk mendapatkan profil pengguna
Future<Map<String, dynamic>?> getUserProfile() async {
@ -270,7 +336,7 @@ class SupabaseService extends GetxService {
try {
final response = await client
.from('stok_bantuan')
.select('*, jenis_bantuan:jenis_bantuan_id(id, nama)');
.select('*, kategori_bantuan:kategori_bantuan_id(id, nama)');
return response;
} catch (e) {
@ -318,18 +384,23 @@ class SupabaseService extends GetxService {
}
}
Future<List<Map<String, dynamic>>?> getJenisBantuan() async {
Future<List<Map<String, dynamic>>?> getKategoriBantuan() async {
try {
final response = await client.from('jenis_bantuan').select('*');
final response = await client.from('kategori_bantuan').select('*');
return response;
} catch (e) {
print('Error getting jenis bantuan: $e');
print('Error getting kategori bantuan: $e');
return null;
}
}
Future<void> addStok(Map<String, dynamic> stokData) async {
try {
print('stokData: $stokData');
// Hapus id dari stokData jika ada, biarkan Supabase yang menghasilkan id
if (stokData.containsKey('id')) {
stokData.remove('id');
}
await client.from('stok_bantuan').insert(stokData);
} catch (e) {
print('Error adding stok: $e');

View File

@ -14,6 +14,35 @@ class DateFormatter {
}
}
static String formatTime(DateTime? time,
{String format = 'HH:mm',
String locale = 'id_ID',
String defaultValue = '-'}) {
if (time == null) return defaultValue;
try {
return DateFormat(format, locale).format(time);
} catch (e) {
print('Error formatting time: $e');
return time
.toString()
.split(' ')[1]
.substring(0, 5); // Fallback to basic format
}
}
static String formatDateTime(DateTime? dateTime,
{String format = 'dd MMMM yyyy HH:mm',
String locale = 'id_ID',
String defaultValue = '-'}) {
if (dateTime == null) return defaultValue;
try {
return DateFormat(format, locale).format(dateTime);
} catch (e) {
print('Error formatting date time: $e');
return dateTime.toString().split('.')[0]; // Fallback to basic format
}
}
static String formatNumber(num? number,
{String locale = 'id_ID', String defaultValue = '0'}) {
if (number == null) return defaultValue;