import 'package:get/get.dart'; import 'package:penyaluran_app/app/data/models/donatur_model.dart'; import 'package:penyaluran_app/app/data/models/petugas_desa_model.dart'; import 'package:penyaluran_app/app/data/models/user_model.dart'; import 'package:penyaluran_app/app/data/models/warga_model.dart'; import 'package:penyaluran_app/app/data/models/lokasi_penyaluran_model.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'dart:io'; class SupabaseService extends GetxService { static SupabaseService get to => Get.find(); late final SupabaseClient client; // Cache untuk profil pengguna Map? _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'); static const String supabaseKey = String.fromEnvironment('SUPABASE_KEY', defaultValue: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzMxODYyODAwLAogICJleHAiOiAxODg5NjI5MjAwCn0.4IpwhwCVbfYXxb8JlZOLSBzCt6kQmypkvuso7N8Aicc'); Future 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) { _isSessionInitialized = true; } else if (event == AuthChangeEvent.signedOut) { _cachedUserProfile = null; _isSessionInitialized = false; } else if (event == AuthChangeEvent.tokenRefreshed) { _isSessionInitialized = true; } }); // Periksa apakah ada sesi yang aktif final session = client.auth.currentSession; if (session != null) { _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 Future signUp(String email, String password) async { return await client.auth.signUp( email: email, password: password, data: {'autoconfirm': true}, ); } // Metode untuk logout Future signOut() async { _cachedUserProfile = null; // Hapus cache saat logout _isSessionInitialized = false; await client.auth.signOut(); } // Metode untuk mendapatkan user saat ini User? get currentUser => client.auth.currentUser; // Metode untuk memeriksa apakah user sudah login 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) { return true; } else { return false; } } print('DEBUG: Tidak ada user atau sesi, user tidak terautentikasi'); return false; } // Metode untuk mendapatkan profil pengguna dasar Future getBaseUserProfile() async { final user = currentUser; if (user == null) return null; try { // Gunakan auth.getUser() daripada mengakses tabel auth.users final userData = await client.auth.getUser(); print('userData: ${userData.user}'); if (userData.user == null) { print('Tidak ada data user ditemukan'); return null; } // Dapatkan role dari tabel khusus roles jika diperlukan String roleName = 'warga'; // default if (userData.user!.userMetadata?['role_id'] != null) { try { final roleResponse = await client .from('roles') .select('role_name') .eq('id', userData.user!.userMetadata!['role_id']) .maybeSingle(); print('roleResponse: $roleResponse'); if (roleResponse != null) { roleName = roleResponse['role_name']; } } catch (e) { print('Error saat mengambil role: $e'); // Lanjutkan dengan role default jika gagal } } // Gabungkan data user dan role Map combinedData = { 'id': userData.user!.id, 'email': userData.user!.email, 'created_at': userData.user!.createdAt, 'updated_at': userData.user!.updatedAt, 'role_id': userData.user!.userMetadata?['role_id'], 'roles': {'role_name': roleName} }; return BaseUserModel.fromJson(combinedData); } catch (e) { print('Error pada getBaseUserProfile: $e'); return null; } } // Metode untuk mendapatkan profil pengguna dengan data lengkap Future?> getUserProfile() async { try { // Jika cache profil tersedia, gunakan if (_cachedUserProfile != null) { return _cachedUserProfile; } // Jika tidak ada cache, ambil dari database final user = currentUser; if (user == null) { print('DEBUG: Tidak ada user yang login'); return null; } final userId = user.id; // Debug info print('DEBUG: Mengambil data user profile untuk ID: $userId'); // Ambil data role dari database final roleResponse = await client .from('users_with_roles') .select('role_name') .eq('id', userId) .maybeSingle(); if (roleResponse == null) { print('DEBUG: Tidak menemukan role untuk user ID: $userId'); return null; } final roleName = roleResponse['role_name']; print('DEBUG: Role name: $roleName'); // Ambil data khusus untuk role tersebut Map? roleData; switch (roleName.toLowerCase()) { case 'warga': final wargaResponse = await client .from('warga') .select( '*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)') .eq('id', userId) .maybeSingle(); roleData = wargaResponse; break; case 'petugas_desa': final petugasResponse = await client .from('petugas_desa') .select( '*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)') .eq('id', userId) .maybeSingle(); roleData = petugasResponse; break; case 'donatur': final donaturResponse = await client .from('donatur') .select('*') .eq('id', userId) .maybeSingle(); roleData = donaturResponse; break; default: print('DEBUG: Role tidak dikenali: $roleName'); return null; } if (roleData == null) { print('DEBUG: Tidak menemukan data untuk role: $roleName'); return null; } // Siapkan data kombinasi dari Supabase Auth + data dari tabel role final combinedData = { 'id': userId, 'email': user.email, 'role': roleName, 'created_at': user.createdAt, 'updated_at': user.updatedAt, 'role_data': roleData, }; // Tambahkan nama dari data role jika ada berdasarkan role switch (roleName.toLowerCase()) { case 'warga': if (roleData['nama_lengkap'] != null) { combinedData['name'] = roleData['nama_lengkap']; } break; case 'petugas_desa': case 'donatur': if (roleData['nama'] != null) { combinedData['name'] = roleData['nama']; } break; } // Tambahkan data role-specific combinedData['role_data'] = roleData; // Tambahkan data desa jika ada if (roleData['desa'] != null) { combinedData['desa'] = roleData['desa']; } // Tambahkan nama dari data role jika ada if (roleData['nama_lengkap'] != null) { combinedData['name'] = roleData['nama_lengkap']; } // Cache profil untuk penggunaan berikutnya _cachedUserProfile = combinedData; print('combinedData: $combinedData'); return combinedData; } catch (e) { print('Error pada getUserProfile: $e'); return null; } } // Metode eksplisit untuk membersihkan cache profil void clearUserProfileCache() { print('DEBUG: Membersihkan cache profil pengguna'); _cachedUserProfile = null; } // Metode untuk mendapatkan data warga Future getWargaProfile(String userId) async { try { final response = await client .from('warga') .select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)') .eq('id', userId) // id di tabel warga = userId .maybeSingle(); if (response == null) { print('Data warga tidak ditemukan'); return null; } return WargaModel.fromJson(response); } catch (e) { print('Error pada getWargaProfile: $e'); return null; } } // Metode untuk mendapatkan data donatur Future getDonaturProfile(String userId) async { try { final response = await client .from('donatur') .select('*') .eq('id', userId) // id di tabel donatur = userId .maybeSingle(); if (response == null) { print('Data donatur tidak ditemukan'); return null; } return DonaturModel.fromJson(response); } catch (e) { print('Error pada getDonaturProfile: $e'); return null; } } // Metode untuk mendapatkan data petugas desa Future getPetugasDesaProfile(String userId) async { try { final response = await client .from('petugas_desa') .select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)') .eq('id', userId) // id di tabel petugas_desa = userId .maybeSingle(); if (response == null) { print('Data petugas desa tidak ditemukan'); return null; } return PetugasDesaModel.fromJson(response); } catch (e) { print('Error pada getPetugasDesaProfile: $e'); return null; } } // Metode untuk mendapatkan data user lengkap berdasarkan role // @deprecated Gunakan AuthProvider.getCurrentUser() sebagai gantinya // Metode ini dipertahankan untuk kompatibilitas mundur Future getUserData() async { print( 'WARNING: Menggunakan metode getUserData() yang sudah deprecated. Gunakan AuthProvider.getCurrentUser() sebagai gantinya.'); final baseUser = await getBaseUserProfile(); if (baseUser == null) return null; try { switch (baseUser.roleName.toLowerCase()) { case 'warga': final wargaData = await getWargaProfile(baseUser.id); if (wargaData != null) { return UserData( baseUser: baseUser, roleData: wargaData, ); } break; case 'donatur': final donaturData = await getDonaturProfile(baseUser.id); if (donaturData != null) { return UserData( baseUser: baseUser, roleData: donaturData, ); } break; case 'petugas_desa': final petugasDesaData = await getPetugasDesaProfile(baseUser.id); if (petugasDesaData != null) { return UserData( baseUser: baseUser, roleData: petugasDesaData, ); } break; } // Jika data role-specific tidak ditemukan print('Data spesifik tidak ditemukan untuk role: ${baseUser.roleName}'); return null; } catch (e) { print('Error pada getUserData: $e'); return null; } } // ==================== PETUGAS DESA METHODS ==================== // Dashboard methods Future getTotalPenerima() async { try { final response = await client.from('warga').select('id').eq('status', 'AKTIF'); return response.length; } catch (e) { print('Error getting total penerima: $e'); return null; } } // Metode untuk mendapatkan data penerima terbaru Future>?> getPenerimaTerbaru() async { try { final response = await client .from('warga') .select('*') .eq('status', 'AKTIF') .order('created_at', ascending: false) .limit(5); return response; } catch (e) { print('Error getting penerima terbaru: $e'); return null; } } // Future getTotalBantuan() async { // try { // final response = await client.from('stok_bantuan').select('jumlah'); // double total = 0; // for (var item in response) { // total += (item['jumlah'] ?? 0); // } // return total.toInt(); // } catch (e) { // print('Error getting total bantuan: $e'); // return null; // } // } Future getTotalPenyaluran() async { try { final response = await client .from('penyaluran_bantuan') .select('id') .eq('status', 'TERLAKSANA'); return response.length; } catch (e) { print('Error getting total penyaluran: $e'); return null; } } // Metode untuk mendapatkan total semua penyaluran (termasuk semua status) Future getTotalSemuaPenyaluran() async { try { final response = await client.from('penyaluran_bantuan').select('id'); return response.length; } catch (e) { print('Error getting total semua penyaluran: $e'); return null; } } // Metode untuk mendapatkan jumlah penyaluran berdasarkan status Future?> getStatusPenyaluran() async { try { final result = { 'dijadwalkan': 0, 'aktif': 0, 'batal': 0, 'terlaksana': 0 }; // Mendapatkan jumlah penyaluran dengan status DIJADWALKAN final dijadwalkanResponse = await client .from('penyaluran_bantuan') .select('id') .eq('status', 'DIJADWALKAN'); result['dijadwalkan'] = dijadwalkanResponse.length; // Mendapatkan jumlah penyaluran dengan status AKTIF final aktifResponse = await client .from('penyaluran_bantuan') .select('id') .eq('status', 'AKTIF'); result['aktif'] = aktifResponse.length; // Mendapatkan jumlah penyaluran dengan status BATAL final batalResponse = await client .from('penyaluran_bantuan') .select('id') .eq('status', 'BATALTERLAKSANA'); result['batal'] = batalResponse.length; // Mendapatkan jumlah penyaluran dengan status TERLAKSANA final terlaksanaResponse = await client .from('penyaluran_bantuan') .select('id') .eq('status', 'TERLAKSANA'); result['terlaksana'] = terlaksanaResponse.length; return result; } catch (e) { print('Error getting status penyaluran: $e'); return null; } } Future>?> getNotifikasiBelumDibaca( String userId) async { try { // Notifikasi masih menggunakan user_id karena tabelnya terpisah final response = await client .from('notifikasi') .select('*') .eq('user_id', userId) .eq('dibaca', false) .order('created_at', ascending: false); return response; } catch (e) { print('Error getting notifikasi belum dibaca: $e'); return null; } } // Jadwal penyaluran methods Future>?> getJadwalAktif() async { try { final response = await client .from('penyaluran_bantuan') .select(''' *, kategori_bantuan(*), lokasi_penyaluran:lokasi_penyaluran_id( id, nama, alamat_lengkap ) ''') .eq('status', 'AKTIF') .order('tanggal_penyaluran', ascending: true); return response; } catch (e) { print('Error getting jadwal aktif: $e'); return null; } } Future>?> getJadwalMendatang() async { try { final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final week = today.add(const Duration(days: 7)); final response = await client .from('penyaluran_bantuan') .select('*') .gte('tanggal_penyaluran', today) .lt('tanggal_penyaluran', week) .inFilter('status', ['DIJADWALKAN']).order('tanggal_penyaluran', ascending: true); return response; } catch (e) { print('Error getting jadwal mendatang: $e'); return null; } } Future>?> getJadwalTerlaksana() async { try { final response = await client .from('penyaluran_bantuan') .select('*') .inFilter('status', ['TERLAKSANA', 'BATALTERLAKSANA']).order( 'tanggal_penyaluran', ascending: false); return response; } catch (e) { print('Error getting jadwal selesai: $e'); return null; } } Future>?> getPermintaanPenjadwalan() async { try { final response = await client .from('penyaluran_bantuan') .select('*') .eq('status', 'DIJADWALKAN'); return response; } catch (e) { print('Error getting permintaan penjadwalan: $e'); return null; } } Future approveJadwal(String jadwalId) async { try { await client.from('penyaluran_bantuan').update({ 'status': 'AKTIF', 'updated_at': DateTime.now().toIso8601String(), }).eq('id', jadwalId); } catch (e) { print('Error approving jadwal: $e'); throw e.toString(); } } Future rejectJadwal(String jadwalId, String alasan) async { try { await client.from('penyaluran_bantuan').update({ 'status': 'BATALTERLAKSANA', 'alasan_penolakan': alasan, 'updated_at': DateTime.now().toIso8601String(), }).eq('id', jadwalId); } catch (e) { print('Error rejecting jadwal: $e'); throw e.toString(); } } Future completeJadwal(String jadwalId) async { try { await client.from('penyaluran_bantuan').update({ 'status': 'TERLAKSANA', 'updated_at': DateTime.now().toIso8601String(), }).eq('id', jadwalId); } catch (e) { print('Error completing jadwal: $e'); throw e.toString(); } } // Metode untuk memperbarui status jadwal Future updateJadwalStatus(String jadwalId, String newStatus) async { try { await client.from('penyaluran_bantuan').update({ 'status': newStatus, 'updated_at': DateTime.now().toUtc().toIso8601String() }).eq('id', jadwalId); print('Jadwal status updated: $jadwalId -> $newStatus'); } catch (e) { print('Error updating jadwal status: $e'); rethrow; } } // Update status jadwal penyaluran secara batch untuk efisiensi Future batchUpdateJadwalStatus( Map jadwalUpdates) async { if (jadwalUpdates.isEmpty) return; try { print('Attempting batch update for ${jadwalUpdates.length} jadwal'); final timestamp = DateTime.now().toUtc().toIso8601String(); // Format data sesuai dengan yang diharapkan oleh SQL function final List> formattedUpdates = jadwalUpdates.entries .map((e) => {'id': e.key, 'status': e.value}) .toList(); print('Formatted updates: $formattedUpdates'); try { // Coba gunakan RPC dulu - kirim sebagai array dari objek JSON final result = await client.rpc('batch_update_jadwal_status', params: { 'jadwal_updates': formattedUpdates, 'updated_timestamp': timestamp, }); print('Batch update via RPC response: $result'); // Periksa hasil untuk mengkonfirmasi berapa banyak yang berhasil diupdate if (result != null) { final bool success = result['success'] == true; final int updatedCount = result['updated_count'] ?? 0; if (success) { print('Successfully updated $updatedCount records via RPC'); // Log ID yang berhasil diupdate final List successIds = List.from(result['success_ids'] ?? []); if (successIds.isNotEmpty) { print( 'Successfully updated jadwal IDs: ${successIds.join(", ")}'); } // Jika ada yang gagal, log untuk debugging if (updatedCount < jadwalUpdates.length) { print( 'Warning: ${jadwalUpdates.length - updatedCount} records failed to update'); // Periksa apakah ada informasi error if (result['errors'] != null) { final int errorCount = result['errors']['count'] ?? 0; if (errorCount > 0) { final List errorIds = List.from(result['errors']['ids'] ?? []); final List errorMessages = List.from(result['errors']['messages'] ?? []); for (int i = 0; i < errorCount; i++) { if (i < errorIds.length && i < errorMessages.length) { print( 'Error updating jadwal ${errorIds[i]}: ${errorMessages[i]}'); } } } } // Update individual yang gagal menggunakan metode satu per satu for (var entry in jadwalUpdates.entries) { if (!successIds.contains(entry.key)) { try { await updateJadwalStatus(entry.key, entry.value); print('Fallback update successful for jadwal ${entry.key}'); } catch (e) { print( 'Fallback update also failed for jadwal ${entry.key}: $e'); } } } } } else { print( 'Batch update reported failure. Falling back to individual updates.'); _fallbackToIndividualUpdates(jadwalUpdates); } } else { print( 'Batch update returned null result. Falling back to individual updates.'); _fallbackToIndividualUpdates(jadwalUpdates); } } catch (rpcError) { print('RPC batch update failed: $rpcError'); print('Falling back to individual updates'); _fallbackToIndividualUpdates(jadwalUpdates); } } catch (e) { print('Error in batch update process: $e'); rethrow; } } // Helper function untuk fallback ke individual updates Future _fallbackToIndividualUpdates( Map jadwalUpdates) async { for (var entry in jadwalUpdates.entries) { try { await updateJadwalStatus(entry.key, entry.value); print('Individual update successful: ${entry.key} -> ${entry.value}'); } catch (updateError) { print('Failed to update jadwal ${entry.key}: $updateError'); } } } // Stok bantuan methods Future>?> getStokBantuan() async { try { final response = await client .from('stok_bantuan') .select('*, kategori_bantuan:kategori_bantuan_id(*, nama)'); return response; } catch (e) { print('Error getting stok bantuan: $e'); return null; } } Future?> getStokStatistics() async { try { // Get stok masuk final masukResponse = await client.from('stok_bantuan').select('jumlah'); double masuk = 0; for (var item in masukResponse) { masuk += (item['jumlah'] ?? 0); } // Get stok keluar final keluarResponse = await client.from('detail_penyaluran').select('jumlah'); double keluar = 0; for (var item in keluarResponse) { keluar += (item['jumlah'] ?? 0); } return { 'masuk': masuk, 'keluar': keluar, }; } catch (e) { print('Error getting stok statistics: $e'); return null; } } Future>?> getBentukBantuan() async { try { final response = await client.from('bentuk_bantuan').select('*'); return response; } catch (e) { print('Error getting bentuk bantuan: $e'); return null; } } Future>?> getKategoriBantuan() async { try { final response = await client.from('kategori_bantuan').select('*'); return response; } catch (e) { print('Error getting kategori bantuan: $e'); return null; } } Future addStok(Map 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'); throw e.toString(); } } Future updateStok(String stokId, Map stok) async { try { await client.from('stok_bantuan').update(stok).eq('id', stokId); } catch (e) { print('Error updating stok: $e'); throw e.toString(); } } Future deleteStok(String stokId) async { try { await client.from('stok_bantuan').delete().eq('id', stokId); } catch (e) { print('Error deleting stok: $e'); throw e.toString(); } } // Penitipan bantuan methods Future>?> getPenitipanBantuan() async { try { final response = await client .from('penitipan_bantuan') .select('*, donatur(*), stok_bantuan:stok_bantuan_id(*)') .order('tanggal_penitipan', ascending: false); return response; } catch (e) { print('Error getting penitipan bantuan: $e'); return null; } } // Metode untuk mendapatkan total penitipan terverifikasi Future getTotalPenitipanTerverifikasi() async { try { final response = await client .from('penitipan_bantuan') .select('id') .eq('status', 'TERVERIFIKASI'); return response.length; } catch (e) { print('Error getting total penitipan terverifikasi: $e'); return null; } } // Metode untuk mengambil data penitipan bantuan dengan status TERVERIFIKASI Future>?> getPenitipanBantuanTerverifikasi() async { try { final response = await client .from('penitipan_bantuan') .select('*, donatur(*), stok_bantuan:stok_bantuan_id(*)') .eq('status', 'TERVERIFIKASI') .order('tanggal_penitipan', ascending: false); return response; } catch (e) { print('Error getting penitipan bantuan terverifikasi: $e'); return null; } } // Upload file methods Future uploadFile( String filePath, String bucket, String folder) async { try { print( 'Uploading file from path: $filePath to bucket: $bucket in folder: $folder'); final fileName = filePath.split('/').last; final fileExt = fileName.split('.').last; final fileKey = '$folder/${DateTime.now().millisecondsSinceEpoch}.$fileExt'; await client.storage.from(bucket).upload( fileKey, File(filePath), fileOptions: const FileOptions(cacheControl: '3600', upsert: true), ); final fileUrl = client.storage.from(bucket).getPublicUrl(fileKey); print('File uploaded: $fileUrl'); return fileUrl; } catch (e) { print('Error uploading file: $e'); return null; } } Future?> uploadMultipleFiles( List filePaths, String bucket, String folder) async { try { final List fileUrls = []; for (final filePath in filePaths) { final fileUrl = await uploadFile(filePath, bucket, folder); if (fileUrl != null) { fileUrls.add(fileUrl); } } return fileUrls; } catch (e) { print('Error uploading multiple files: $e'); return null; } } Future verifikasiPenitipan( String penitipanId, String fotoBuktiSerahTerimaPath) async { try { // Upload bukti serah terima final fotoBuktiSerahTerimaUrl = await uploadFile( fotoBuktiSerahTerimaPath, 'bantuan', 'foto_bukti_serah_terima'); if (fotoBuktiSerahTerimaUrl == null) { throw 'Gagal mengupload bukti serah terima'; } final petugasDesaId = client.auth.currentUser?.id; if (petugasDesaId == null) { throw 'ID petugas desa tidak ditemukan'; } print( 'Verifikasi penitipan dengan ID: $penitipanId oleh petugas desa ID: $petugasDesaId'); // 1. Dapatkan data penitipan untuk mendapatkan stok_bantuan_id dan jumlah final response = await client .from('penitipan_bantuan') .select('stok_bantuan_id, jumlah') .eq('id', penitipanId); if (response.isEmpty) { throw 'Data penitipan tidak ditemukan'; } final penitipanData = response[0]; final String stokBantuanId = penitipanData['stok_bantuan_id']; final double jumlah = penitipanData['jumlah'] is int ? penitipanData['jumlah'].toDouble() : penitipanData['jumlah']; // 2. Update status penitipan menjadi terverifikasi final updateData = { 'status': 'TERVERIFIKASI', 'tanggal_verifikasi': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(), 'foto_bukti_serah_terima': fotoBuktiSerahTerimaUrl, 'petugas_desa_id': petugasDesaId, }; print('Data yang akan diupdate: $updateData'); await client .from('penitipan_bantuan') .update(updateData) .eq('id', penitipanId); // 3. Tambahkan ke stok dan catat di riwayat stok await tambahStokDariPenitipan( penitipanId, stokBantuanId, jumlah, petugasDesaId); print('Penitipan berhasil diverifikasi dan stok bantuan ditambahkan'); } catch (e) { print('Error verifying penitipan: $e'); throw e.toString(); } } Future tolakPenitipan(String penitipanId, String alasan) async { try { await client.from('penitipan_bantuan').update({ 'status': 'DITOLAK', 'alasan_penolakan': alasan, 'updated_at': DateTime.now().toIso8601String(), }).eq('id', penitipanId); } catch (e) { print('Error rejecting penitipan: $e'); throw e.toString(); } } // Metode untuk menambahkan penitipan bantuan baru Future tambahPenitipanBantuan({ required String stokBantuanId, required double jumlah, required String deskripsi, required List fotoBantuanPaths, String? donaturId, bool isUang = false, }) async { try { final petugasDesaId = client.auth.currentUser?.id; if (petugasDesaId == null) { throw 'User tidak ditemukan'; } // Upload foto bantuan final fotoBantuanUrls = await uploadMultipleFiles( fotoBantuanPaths, 'bantuan', 'foto_bantuan'); if (fotoBantuanUrls == null || fotoBantuanUrls.isEmpty) { throw 'Gagal mengupload foto bantuan'; } // Data penitipan final penitipanData = { 'stok_bantuan_id': stokBantuanId, 'jumlah': jumlah, 'deskripsi': deskripsi, 'status': 'TERVERIFIKASI', // Langsung terverifikasi karena diinput oleh petugas desa 'foto_bantuan': fotoBantuanUrls, 'tanggal_penitipan': DateTime.now().toIso8601String(), 'tanggal_verifikasi': DateTime.now().toIso8601String(), 'created_at': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(), 'petugas_desa_id': petugasDesaId, 'is_uang': isUang, }; // Tambahkan donatur_id jika ada if (donaturId != null && donaturId.isNotEmpty) { penitipanData['donatur_id'] = donaturId; } await client.from('penitipan_bantuan').insert(penitipanData); } catch (e) { print('Error adding penitipan bantuan: $e'); throw e.toString(); } } Future?> getDonaturById(String donaturId) async { try { final response = await client.from('donatur').select('*').eq('id', donaturId).single(); return response; } catch (e) { print('Error getting donatur by id: $e'); return null; } } // Metode untuk mencari donatur berdasarkan keyword Future>?> searchDonatur(String keyword) async { try { if (keyword.length < 3) { return []; } final response = await client .from('donatur') .select('*') .ilike('nama', '%$keyword%') .limit(10); return response; } catch (e) { print('Error searching donatur: $e'); return null; } } // Metode untuk mendapatkan daftar donatur Future>?> getDaftarDonatur() async { try { final response = await client .from('donatur') .select('*') .order('nama_lengkap', ascending: true); return response; } catch (e) { print('Error getting daftar donatur: $e'); return null; } } // Metode untuk menambahkan donatur baru Future tambahDonatur(Map donaturData) async { try { // Pastikan field nama_lengkap ada di donaturData if (donaturData.containsKey('nama')) { donaturData['nama_lengkap'] = donaturData['nama']; donaturData.remove('nama'); } final response = await client.from('donatur').insert(donaturData).select('id'); if (response.isNotEmpty) { return response[0]['id']; } return null; } catch (e) { print('Error adding donatur: $e'); throw e.toString(); } } // Pengaduan methods Future>?> getPengaduan() async { try { final response = await client.from('pengaduan').select('*'); return response; } catch (e) { print('Error getting pengaduan: $e'); return null; } } // Metode untuk mendapatkan pengaduan dengan detail penerima penyaluran Future>?> getPengaduanWithPenerimaPenyaluran() async { try { final response = await client.from('pengaduan').select(''' *, penerima_penyaluran:penerima_penyaluran_id( *, penyaluran_bantuan:penyaluran_bantuan_id(*), stok_bantuan:stok_bantuan_id(*), warga:warga_id(*) ), warga:warga_id(*) ''').order('created_at', ascending: false); return response; } catch (e) { print('Error getting pengaduan with penerima penyaluran: $e'); return null; } } // Metode untuk mendapatkan pengaduan warga tertentu dengan detail penerima penyaluran Future>?> getPengaduanWargaWithPenerimaPenyaluran( String wargaId) async { try { final response = await client.from('pengaduan').select(''' *, penerima_penyaluran:penerima_penyaluran_id( *, penyaluran_bantuan:penyaluran_bantuan_id(*), stok_bantuan:stok_bantuan_id(*), warga:warga_id(*) ), warga:warga_id(*) ''').eq('warga_id', wargaId).order('created_at', ascending: false); return response; } catch (e) { print('Error getting warga pengaduan with penerima penyaluran: $e'); return null; } } Future prosesPengaduan(String pengaduanId) async { try { await client.from('pengaduan').update({ 'status': 'DIPROSES', 'updated_at': DateTime.now().toIso8601String(), }).eq('id', pengaduanId); } catch (e) { print('Error processing pengaduan: $e'); throw e.toString(); } } Future tambahTindakanPengaduan(Map tindakan) async { try { await client.from('tindakan_pengaduan').insert(tindakan); } catch (e) { print('Error adding tindakan pengaduan: $e'); throw e.toString(); } } Future updateTindakanPengaduan( String tindakanId, Map tindakan) async { try { await client .from('tindakan_pengaduan') .update(tindakan) .eq('id', tindakanId); } catch (e) { print('Error updating tindakan pengaduan: $e'); throw e.toString(); } } Future updateStatusPengaduan(String pengaduanId, String status) async { try { await client.from('pengaduan').update({ 'status': status, 'updated_at': DateTime.now().toIso8601String(), }).eq('id', pengaduanId); } catch (e) { print('Error updating status pengaduan: $e'); throw e.toString(); } } Future>?> getTindakanPengaduan( String pengaduanId) async { try { final response = await client .from('tindakan_pengaduan') .select(''' *, petugas:petugas_id(id, nama_lengkap, nip) ''') .eq('pengaduan_id', pengaduanId) .order('created_at', ascending: false); return response; } catch (e) { print('Error getting tindakan pengaduan: $e'); return null; } } // Metode untuk menambahkan feedback dan rating pengaduan Future addPengaduanFeedback( String pengaduanId, String feedback, int rating) async { try { await client.from('pengaduan').update({ 'feedback_warga': feedback, 'rating_warga': rating, 'updated_at': DateTime.now().toIso8601String(), }).eq('id', pengaduanId); } catch (e) { print('Error adding pengaduan feedback: $e'); throw e.toString(); } } // Metode untuk memperbarui feedback dan rating pengaduan Future updatePengaduanFeedback( String pengaduanId, String feedback, int rating) async { try { await client.from('pengaduan').update({ 'feedback_warga': feedback, 'rating_warga': rating, 'updated_at': DateTime.now().toIso8601String(), }).eq('id', pengaduanId); } catch (e) { print('Error updating pengaduan feedback: $e'); throw e.toString(); } } // Penerima bantuan methods Future>?> getPenerimaBantuan() async { try { final response = await client.from('warga').select('*'); return response; } catch (e) { print('Error getting penerima bantuan: $e'); return null; } } // Metode untuk mendapatkan data penyaluran bantuan berdasarkan ID warga Future>?> getPenyaluranBantuanByWargaId( String wargaId) async { try { // Pertama, cari warga berdasarkan NIP untuk mendapatkan UUID-nya final wargaResponse = await client .from('warga') .select('id') .eq('id', wargaId) .maybeSingle(); if (wargaResponse == null) { print('Warning: Warga dengan NIP $wargaId tidak ditemukan'); return []; } final wargaUuid = wargaResponse['id']; // Kemudian gunakan UUID untuk mencari penyaluran bantuan final response = await client.from('penerima_penyaluran').select(''' *, penyaluran_bantuan:penyaluran_bantuan_id( *, kategori_bantuan(*), lokasi_penyaluran:lokasi_penyaluran_id( id, nama, alamat_lengkap ) ) ''').eq('warga_id', wargaUuid).order('created_at', ascending: false); return response; } catch (e) { print('Error getting penyaluran bantuan by warga id: $e'); return null; } } Future tambahPenerima(Map penerima) async { try { await client.from('warga').insert(penerima); } catch (e) { print('Error adding penerima: $e'); throw e.toString(); } } Future updatePenerima( String penerimaId, Map penerima) async { try { await client.from('warga').update(penerima).eq('id', penerimaId); } catch (e) { print('Error updating penerima: $e'); throw e.toString(); } } Future updateStatusPenerima(String penerimaId, String status) async { try { await client.from('warga').update({ 'status': status, 'updated_at': DateTime.now().toIso8601String(), }).eq('id', penerimaId); } catch (e) { print('Error updating status penerima: $e'); throw e.toString(); } } // Laporan methods Future>?> getLaporan( DateTime? tanggalMulai, DateTime? tanggalSelesai) async { try { var query = client.from('laporan').select('*'); if (tanggalMulai != null) { query = query.gte('created_at', tanggalMulai.toIso8601String()); } if (tanggalSelesai != null) { query = query.lte('created_at', tanggalSelesai.toIso8601String()); } final response = await query.order('created_at', ascending: false); return response; } catch (e) { print('Error getting laporan: $e'); return null; } } Future generateLaporan(Map laporan) async { try { final response = await client.from('laporan').insert(laporan); return response[0]['id']; } catch (e) { print('Error generating laporan: $e'); throw e.toString(); } } Future downloadLaporan(String laporanId) async { try { final response = await client .from('laporan') .select('file_urls') .eq('id', laporanId) .single(); final fileUrls = response['file_urls']; if (fileUrls != null && fileUrls.isNotEmpty) { return fileUrls[0]; } return null; } catch (e) { print('Error downloading laporan: $e'); return null; } } Future deleteLaporan(String laporanId) async { try { await client.from('laporan').delete().eq('id', laporanId); } catch (e) { print('Error deleting laporan: $e'); throw e.toString(); } } // Metode untuk mendapatkan data warga berdasarkan user ID Future?> getWargaByUserId() async { try { final user = currentUser; if (user == null) return null; final response = await client .from('warga') .select('*') .eq('id', user.id) .maybeSingle(); return response; } catch (e) { print('Error getting warga data: $e'); return null; } } // Metode untuk membuat profil warga Future createWargaProfile({ required String nik, required String namaLengkap, required String jenisKelamin, String? noHp, String? alamat, String? tempatLahir, DateTime? tanggalLahir, String? agama, }) async { try { final user = currentUser; if (user == null) throw 'User tidak ditemukan'; // Dapatkan role_id untuk warga final roleResponse = await client .from('roles') .select('id') .eq('role_name', 'warga') .single(); final roleId = roleResponse['id']; // Update role_id di auth.users await client .from('auth.users') .update({'role_id': roleId}).eq('id', user.id); // Buat profil warga await client.from('warga').insert({ 'id': user.id, // Gunakan id dari auth.users sebagai id di tabel warga 'nik': nik, 'nama_lengkap': namaLengkap, 'jenis_kelamin': jenisKelamin, 'no_hp': noHp, 'alamat': alamat, 'tempat_lahir': tempatLahir, 'tanggal_lahir': tanggalLahir?.toIso8601String(), 'agama': agama, 'status': 'MENUNGGU_VERIFIKASI', 'created_at': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(), }); } catch (e) { print('Error creating warga profile: $e'); throw e.toString(); } } // Metode untuk membuat profil donatur Future createDonaturProfile({ required String nama_lengkap, String? alamat, String? noHp, String? email, String? jenis, String? deskripsi, }) async { try { final user = currentUser; if (user == null) throw 'User tidak ditemukan'; // Dapatkan role_id untuk donatur final roleResponse = await client .from('roles') .select('id') .eq('role_name', 'donatur') .single(); final roleId = roleResponse['id']; // Update role_id di auth.users await client .from('auth.users') .update({'role_id': roleId}).eq('id', user.id); // Buat profil donatur await client.from('donatur').insert({ 'id': user.id, // Gunakan id dari auth.users sebagai id di tabel donatur 'nama_lengkap': nama_lengkap, 'alamat': alamat, 'no_hp': noHp, 'email': email, 'jenis': jenis, 'deskripsi': deskripsi, 'status': 'AKTIF', 'created_at': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(), }); } catch (e) { print('Error creating donatur profile: $e'); throw e.toString(); } } // Metode untuk memperbarui profil donatur Future updateDonaturProfile({ required String userId, required String nama, required String email, String? noHp, String? fotoProfil, }) async { try { // Buat map untuk update data final Map updateData = { 'nama_lengkap': nama, // Untuk konsistensi dengan field nama_lengkap 'no_hp': noHp, 'updated_at': DateTime.now().toIso8601String(), }; // Tambahkan foto profil jika ada if (fotoProfil != null) { // Jika string kosong, set null untuk menghapus foto profil if (fotoProfil.isEmpty) { updateData['foto_profil'] = null; } else { updateData['foto_profil'] = fotoProfil; } } // Update data donatur await client.from('donatur').update(updateData).eq('id', userId); // Update email di auth.users jika berubah if (email != client.auth.currentUser?.email) { // Gunakan metode updateUserEmail await client.auth.updateUser(UserAttributes( email: email, )); } // Hapus cache user profile _cachedUserProfile = null; print('Cache profil user dihapus setelah update donatur'); } catch (e) { print('Error updating donatur profile: $e'); throw e.toString(); } } // Metode untuk membuat profil petugas desa Future createPetugasDesaProfile({ required String nama_lengkap, String? alamat, String? noHp, String? email, String? jabatan, String? nip, required String desa_id, }) async { try { final user = currentUser; if (user == null) throw 'User tidak ditemukan'; // Dapatkan role_id untuk petugas desa final roleResponse = await client .from('roles') .select('id') .eq('role_name', 'petugas_desa') .single(); final roleId = roleResponse['id']; // Update role_id di auth.users await client .from('auth.users') .update({'role_id': roleId}).eq('id', user.id); // Buat profil petugas desa await client.from('petugas_desa').insert({ 'id': user .id, // Gunakan id dari auth.users sebagai id di tabel petugas_desa 'nama_lengkap': nama_lengkap, 'alamat': alamat, 'no_hp': noHp, 'email': email, 'jabatan': jabatan, 'nip': nip, 'desa_id': desa_id, 'status': 'AKTIF', 'created_at': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(), }); } catch (e) { print('Error creating petugas desa profile: $e'); throw e.toString(); } } // Metode untuk mendapatkan notifikasi pengguna Future>> getUserNotifications( {bool unreadOnly = false}) async { try { final user = currentUser; if (user == null) return []; // Notifikasi masih menggunakan user_id karena tabelnya terpisah final query = unreadOnly ? client .from('notifikasi') .select('*') .eq('user_id', user.id) .eq('dibaca', false) .order('created_at', ascending: false) : client .from('notifikasi') .select('*') .eq('user_id', user.id) .order('created_at', ascending: false); final response = await query; return response; } catch (e) { print('Error getting user notifications: $e'); return []; } } // Metode untuk menandai notifikasi sebagai telah dibaca Future markNotificationAsRead(int notificationId) async { try { await client.from('notifikasi').update({ 'dibaca': true, 'updated_at': DateTime.now().toIso8601String(), }).eq('id', notificationId); } catch (e) { print('Error marking notification as read: $e'); throw e.toString(); } } // Metode untuk mendapatkan informasi petugas desa berdasarkan ID Future?> getPetugasDesaById(String petugasDesaId) async { try { print('Mengambil data petugas desa dengan ID: $petugasDesaId'); // Gunakan tabel petugas_desa sebagai pengganti user_profile final response = await client .from('petugas_desa') .select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)') .eq('id', petugasDesaId) .maybeSingle(); print('Response: $response'); if (response != null) { print('Berhasil mendapatkan data petugas desa: $response'); return response; } print('Data petugas desa tidak ditemukan untuk ID: $petugasDesaId'); return null; } catch (e) { print('Error getting petugas desa by ID: $e'); return null; } } // Metode untuk mendapatkan semua lokasi penyaluran Future>?> getAllLokasiPenyaluran() async { try { final response = await client.from('lokasi_penyaluran').select('*'); return response; } catch (e) { print('Error getting all lokasi penyaluran: $e'); return null; } } // Metode untuk mendapatkan lokasi penyaluran berdasarkan ID petugas Future> getLokasiPenyaluran( {String? petugasId}) async { try { var query = client.from('lokasi_penyaluran').select('*'); final response = await query.order('nama'); return response .map((data) => LokasiPenyaluranModel.fromJson(data)) .toList(); } catch (e) { print('Error getting lokasi penyaluran: $e'); return []; } } // Metode untuk menghapus lokasi penyaluran Future deleteLokasiPenyaluran(String lokasiId) async { try { await client.from('lokasi_penyaluran').delete().eq('id', lokasiId); } catch (e) { print('Error deleting lokasi penyaluran: $e'); throw e.toString(); } } // Metode untuk mendapatkan daftar penerima penyaluran berdasarkan ID penyaluran Future>?> getPenerimaPenyaluran( String penyaluranId) async { // Metode ini tidak lagi mengambil data dari database // Gunakan data dummy dari controller return []; } // // Metode untuk memperbarui status penerimaan bantuan // Future updateStatusPenerimaan(int penerimaId, String status, // {DateTime? tanggalPenerimaan, // String? buktiPenerimaan, // String? keterangan}) async { // try { // // Periksa petugas ID // final petugasId = client.auth.currentUser?.id; // if (petugasId == null) { // throw Exception('ID petugas tidak ditemukan'); // } // final Map updateData = { // 'status_penerimaan': status, // }; // if (tanggalPenerimaan != null) { // updateData['tanggal_penerimaan'] = tanggalPenerimaan.toIso8601String(); // } // if (buktiPenerimaan != null) { // updateData['bukti_penerimaan'] = buktiPenerimaan; // } // if (keterangan != null) { // updateData['keterangan'] = keterangan; // } // // Update status penerimaan // await client // .from('penerima_penyaluran') // .update(updateData) // .eq('id', penerimaId); // // Jika status adalah DITERIMA, kurangi stok // if (status.toUpperCase() == 'DITERIMA') { // // Dapatkan data penerima penyaluran (stok_bantuan_id dan jumlah) // final penerimaData = await client // .from('penerima_penyaluran') // .select('penyaluran_bantuan_id, stok_bantuan_id, jumlah') // .eq('id', penerimaId) // .single(); // if (penerimaData != null) { // final String penyaluranId = penerimaData['penyaluran_bantuan_id']; // final String stokBantuanId = penerimaData['stok_bantuan_id']; // final double jumlah = penerimaData['jumlah'] is int // ? penerimaData['jumlah'].toDouble() // : penerimaData['jumlah']; // // Kurangi stok dan catat riwayat // await kurangiStokDariPenyaluran( // penyaluranId, stokBantuanId, jumlah, petugasId); // } // } // return true; // } catch (e) { // print('Error updating status penerimaan: $e'); // return false; // } // } // Metode untuk mendapatkan semua kategori bantuan Future>?> getAllKategoriBantuan() async { try { final response = await client .from('kategori_bantuan') .select('*') .order('nama', ascending: true); return response; } catch (e) { print('Error getting all kategori bantuan: $e'); return null; } } // Metode untuk memeriksa koneksi ke Supabase Future checkConnection() async { try { print('DEBUG SERVICE: Memeriksa koneksi ke Supabase...'); // Coba melakukan query sederhana final response = await client.from('penerima_penyaluran').select('count').limit(1); print('DEBUG SERVICE: Koneksi berhasil, response: $response'); return true; } catch (e) { print('DEBUG SERVICE: Error saat memeriksa koneksi: $e'); return false; } } // Metode untuk mendapatkan data warga berdasarkan ID Future?> getWargaById(String wargaId) async { // Metode ini tidak lagi mengambil data dari database // Gunakan data dummy return { 'id': wargaId, 'nama_lengkap': 'Warga Dummy', 'nik': '1234567890123456', 'alamat': 'Alamat Dummy', 'jenis_kelamin': 'L', 'tanggal_lahir': '1990-01-01', }; } // Metode untuk mencetak struktur data ke konsol void printDataStructure(dynamic data, {String prefix = ''}) { if (data == null) { print('$prefix Data: null'); return; } if (data is List) { print('$prefix Data adalah List dengan ${data.length} item'); if (data.isNotEmpty) { print('$prefix Contoh item pertama:'); printDataStructure(data.first, prefix: '$prefix '); } return; } if (data is Map) { print( '$prefix Data adalah Map dengan keys: ${data.keys.toList().join(', ')}'); // Cek apakah ada key warga if (data.containsKey('warga')) { print('$prefix Data memiliki key "warga"'); print('$prefix Tipe data warga: ${data['warga'].runtimeType}'); printDataStructure(data['warga'], prefix: '$prefix warga: '); } // Cek apakah ada key warga_id if (data.containsKey('warga_id')) { print('$prefix Data memiliki key "warga_id": ${data['warga_id']}'); } return; } // Tipe data lainnya print('$prefix Data: $data (${data.runtimeType})'); } // Fungsi untuk menambahkan penyaluran baru Future> tambahPenyaluran( Map penyaluran) async { try { final response = await client .from('penyaluran_bantuan') .insert(penyaluran) .select() .single(); return response; } catch (e) { print('Error menambahkan penyaluran: $e'); throw e.toString(); } } Future>?> getAllSkemaBantuan() async { try { final response = await client .from('xx02_skema_bantuan') .select('*') .order('created_at', ascending: false); return response; } catch (e) { print('Error getting all skema bantuan: $e'); return null; } } // Metode untuk update profil warga Future updateWargaProfile({ required String userId, required String namaLengkap, required String email, String? noHp, String? fotoProfil, }) async { try { // Buat map untuk update data final Map updateData = { 'nama_lengkap': namaLengkap, 'no_hp': noHp, 'updated_at': DateTime.now().toIso8601String(), }; // Tambahkan foto profil jika ada if (fotoProfil != null) { // Jika string kosong, set null untuk menghapus foto profil if (fotoProfil.isEmpty) { updateData['foto_profil'] = null; } else { updateData['foto_profil'] = fotoProfil; } } // Update data warga await client.from('warga').update(updateData).eq('id', userId); // Update email di auth.users jika berubah if (email != client.auth.currentUser?.email) { // Gunakan metode updateUserEmail await client.auth.updateUser(UserAttributes( email: email, )); } // Hapus cache user profile _cachedUserProfile = null; print('Cache profil user dihapus setelah update warga'); } catch (e) { print('Error updating warga profile: $e'); throw e.toString(); } } // Metode untuk memperbarui profil petugas desa Future updatePetugasDesaProfile({ required String userId, required String nama, required String email, String? noHp, String? fotoProfil, }) async { try { // Buat map untuk update data final Map updateData = { 'nama_lengkap': nama, // Untuk konsistensi dengan field nama_lengkap 'no_hp': noHp, 'updated_at': DateTime.now().toIso8601String(), }; // Tambahkan foto profil jika ada if (fotoProfil != null) { // Jika string kosong, set null untuk menghapus foto profil if (fotoProfil.isEmpty) { updateData['foto_profil'] = null; } else { updateData['foto_profil'] = fotoProfil; } } // Update data petugas desa await client.from('petugas_desa').update(updateData).eq('id', userId); // Update email di auth.users jika berubah if (email != client.auth.currentUser?.email) { // Gunakan metode updateUserEmail await client.auth.updateUser(UserAttributes( email: email, )); } // Hapus cache user profile _cachedUserProfile = null; print('Cache profil user dihapus setelah update petugas desa'); } catch (e) { print('Error updating petugas desa profile: $e'); throw e.toString(); } } // Metode untuk ganti password Future changePassword( String currentPassword, String newPassword) async { try { await client.auth.updateUser( UserAttributes( password: newPassword, ), ); } catch (e) { print('Error changing password: $e'); throw e.toString(); } } // Riwayat Stok methods Future>?> getRiwayatStok( {String? stokBantuanId, String? jenisPerubahan}) async { try { var filterString = ''; if (stokBantuanId != null) { filterString += 'stok_bantuan_id.eq.$stokBantuanId'; } if (jenisPerubahan != null) { filterString += '${filterString.isNotEmpty ? ',' : ''}jenis_perubahan.eq.$jenisPerubahan'; } final response = await client.from('riwayat_stok').select(''' *, stok_bantuan:stok_bantuan_id(*), petugas_desa:created_by_id(*) ''').order('created_at', ascending: false); var result = response; if (filterString.isNotEmpty) { // Menerapkan filter secara manual karena response sudah berupa List result = result.where((item) { if (stokBantuanId != null && item['stok_bantuan_id'] != stokBantuanId) { return false; } if (jenisPerubahan != null && item['jenis_perubahan'] != jenisPerubahan) { return false; } return true; }).toList(); } return result; } catch (e) { print('Error getting riwayat stok: $e'); return null; } } // Metode untuk mencatat penambahan stok dari penitipan Future tambahStokDariPenitipan(String penitipanId, String stokBantuanId, double jumlah, String petugasId) async { try { // 1. Update stok bantuan - tambahkan jumlah final stokBantuanResponse = await client .from('stok_bantuan') .select('total_stok') .eq('id', stokBantuanId) .single(); // Konversi total_stok ke double terlepas dari apakah itu int atau double double currentStok = 0.0; if (stokBantuanResponse['total_stok'] != null) { if (stokBantuanResponse['total_stok'] is int) { currentStok = stokBantuanResponse['total_stok'].toDouble(); } else { currentStok = stokBantuanResponse['total_stok']; } } double newStok = currentStok + jumlah; // Update stok bantuan await client.from('stok_bantuan').update({ 'total_stok': newStok, 'updated_at': DateTime.now().toIso8601String() }).eq('id', stokBantuanId); // 2. Catat riwayat penambahan await client.from('riwayat_stok').insert({ 'stok_bantuan_id': stokBantuanId, 'jenis_perubahan': 'penambahan', 'jumlah': jumlah, 'sumber': 'penitipan', 'id_referensi': penitipanId, 'created_by_id': petugasId, 'created_at': DateTime.now().toIso8601String() }); print('Stok berhasil ditambahkan dari penitipan'); } catch (e) { print('Error adding stok from penitipan: $e'); rethrow; // Re-throw untuk penanganan di tingkat yang lebih tinggi } } // Metode untuk mencatat pengurangan stok dari penyaluran Future kurangiStokDariPenyaluran(String penyaluranId, String stokBantuanId, double jumlah, String petugasId) async { try { // 1. Update stok bantuan - kurangi jumlah final stokBantuanResponse = await client .from('stok_bantuan') .select('total_stok') .eq('id', stokBantuanId) .single(); // Konversi total_stok ke double terlepas dari apakah itu int atau double double currentStok = 0.0; if (stokBantuanResponse['total_stok'] != null) { if (stokBantuanResponse['total_stok'] is int) { currentStok = stokBantuanResponse['total_stok'].toDouble(); } else { currentStok = stokBantuanResponse['total_stok']; } } // Validasi stok cukup if (currentStok < jumlah) { throw Exception('Stok tidak mencukupi untuk pengurangan'); } double newStok = currentStok - jumlah; // Update stok bantuan await client.from('stok_bantuan').update({ 'total_stok': newStok, 'updated_at': DateTime.now().toIso8601String() }).eq('id', stokBantuanId); // 2. Catat riwayat pengurangan await client.from('riwayat_stok').insert({ 'stok_bantuan_id': stokBantuanId, 'jenis_perubahan': 'pengurangan', 'jumlah': jumlah, 'sumber': 'penerimaan', 'id_referensi': penyaluranId, 'created_by_id': petugasId, 'created_at': DateTime.now().toIso8601String() }); print('Stok berhasil dikurangi dari penyaluran'); } catch (e) { print('Error reducing stok from penyaluran: $e'); rethrow; // Re-throw untuk penanganan di tingkat yang lebih tinggi } } // Metode untuk penambahan stok manual oleh petugas Future tambahStokManual({ required String stokBantuanId, required double jumlah, required String alasan, required String fotoBuktiPath, required String petugasId, }) async { try { // 1. Upload foto bukti jika disediakan String fotoBuktiUrl = ''; if (fotoBuktiPath.isNotEmpty) { final String fileName = '${DateTime.now().millisecondsSinceEpoch}_$stokBantuanId.jpg'; final fileResponse = await client.storage.from('stok_bukti').upload( fileName, File(fotoBuktiPath), fileOptions: const FileOptions(cacheControl: '3600', upsert: false), ); fotoBuktiUrl = client.storage.from('stok_bukti').getPublicUrl(fileName); } // 2. Update stok bantuan - tambahkan jumlah final stokBantuanResponse = await client .from('stok_bantuan') .select('total_stok') .eq('id', stokBantuanId) .single(); // Konversi total_stok ke double terlepas dari apakah itu int atau double double currentStok = 0.0; if (stokBantuanResponse['total_stok'] != null) { if (stokBantuanResponse['total_stok'] is int) { currentStok = stokBantuanResponse['total_stok'].toDouble(); } else { currentStok = stokBantuanResponse['total_stok']; } } double newStok = currentStok + jumlah; // Update stok bantuan await client.from('stok_bantuan').update({ 'total_stok': newStok, 'updated_at': DateTime.now().toIso8601String() }).eq('id', stokBantuanId); // 3. Catat riwayat penambahan await client.from('riwayat_stok').insert({ 'stok_bantuan_id': stokBantuanId, 'jenis_perubahan': 'penambahan', 'jumlah': jumlah, 'sumber': 'manual', 'alasan': alasan, 'foto_bukti': fotoBuktiUrl, 'created_by_id': petugasId, 'created_at': DateTime.now().toIso8601String() }); print('Stok berhasil ditambahkan secara manual'); } catch (e) { print('Error adding stok manually: $e'); rethrow; // Re-throw untuk penanganan di tingkat yang lebih tinggi } } // Metode untuk pengurangan stok manual oleh petugas Future kurangiStokManual({ required String stokBantuanId, required double jumlah, required String alasan, required String fotoBuktiPath, required String petugasId, }) async { try { // 1. Validasi stok yang tersedia final stokBantuanResponse = await client .from('stok_bantuan') .select('total_stok') .eq('id', stokBantuanId) .single(); // Konversi total_stok ke double terlepas dari apakah itu int atau double double currentStok = 0.0; if (stokBantuanResponse['total_stok'] != null) { if (stokBantuanResponse['total_stok'] is int) { currentStok = stokBantuanResponse['total_stok'].toDouble(); } else { currentStok = stokBantuanResponse['total_stok']; } } // Validasi stok cukup if (currentStok < jumlah) { throw Exception('Stok tidak mencukupi untuk pengurangan'); } // 2. Upload foto bukti jika disediakan String fotoBuktiUrl = ''; if (fotoBuktiPath.isNotEmpty) { final String fileName = '${DateTime.now().millisecondsSinceEpoch}_$stokBantuanId.jpg'; final fileResponse = await client.storage.from('stok_bukti').upload( fileName, File(fotoBuktiPath), fileOptions: const FileOptions(cacheControl: '3600', upsert: false), ); fotoBuktiUrl = client.storage.from('stok_bukti').getPublicUrl(fileName); } // 3. Update stok bantuan - kurangi jumlah double newStok = currentStok - jumlah; // Update stok bantuan await client.from('stok_bantuan').update({ 'total_stok': newStok, 'updated_at': DateTime.now().toIso8601String() }).eq('id', stokBantuanId); // 4. Catat riwayat pengurangan await client.from('riwayat_stok').insert({ 'stok_bantuan_id': stokBantuanId, 'jenis_perubahan': 'pengurangan', 'jumlah': jumlah, 'sumber': 'manual', 'alasan': alasan, 'foto_bukti': fotoBuktiUrl, 'created_by_id': petugasId, 'created_at': DateTime.now().toIso8601String() }); print('Stok berhasil dikurangi secara manual'); } catch (e) { print('Error reducing stok manually: $e'); rethrow; // Re-throw untuk penanganan di tingkat yang lebih tinggi } } // Tambahkan metode untuk mendapatkan data penitipan berdasarkan ID Future?> getPenitipanById(String id) async { try { final response = await client.from('penitipan_bantuan').select(''' *, donatur:donatur_id(*), petugas_desa:petugas_desa_id(*) ''').eq('id', id).single(); return response; } catch (e) { print('Error getting penitipan by id: $e'); return null; } } // Tambahkan metode untuk mendapatkan data penerimaan berdasarkan ID Future?> getPenerimaanById(String id) async { try { final response = await client.from('penerima_penyaluran').select(''' *, warga:warga_id(*), penyaluran_bantuan:penyaluran_bantuan_id(*, petugas_desa:petugas_id(*) ) ''').eq('id', id).single(); return response; } catch (e) { print('Error getting penerimaan by id: $e'); return null; } } // Metode untuk mengupdate role user dengan SQL langsung Future updateUserRole(String userId, int roleId) async { try { // Coba update via RPC jika sudah diimplementasikan try { await client.rpc( 'update_user_role', params: { 'user_id': userId, 'role_id_value': roleId, }, ); print('DEBUG: Berhasil mengupdate role_id user via RPC'); return; } catch (e) { print('WARN: RPC update_user_role tidak tersedia: $e'); } // Jika RPC tidak tersedia, gunakan query SQL langsung ke auth.users jika diizinkan try { print('DEBUG: Mencoba update auth.users langsung'); await client .from('auth.users') .update({'role_id': roleId}).eq('id', userId); print('DEBUG: Berhasil mengupdate role_id user via SQL langsung'); } catch (e) { print('ERROR: Gagal mengupdate auth.users: $e'); print('INFO: Role perlu diupdate manual melalui dashboard Supabase'); } } catch (e) { print('ERROR: Gagal mengupdate role user: $e'); // Tidak perlu throw exception, kegagalan update role tidak fatal } } }