Files
penyaluran_app/lib/app/services/supabase_service.dart

2374 lines
70 KiB
Dart

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<SupabaseService>();
late final SupabaseClient client;
// 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');
static const String supabaseKey = String.fromEnvironment('SUPABASE_KEY',
defaultValue:
'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) {
_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<AuthResponse> signUp(String email, String password) async {
return await client.auth.signUp(
email: email,
password: password,
data: {'autoconfirm': true},
);
}
// Metode untuk logout
Future<void> 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<BaseUserModel?> 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<String, dynamic> 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<Map<String, dynamic>?> 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<String, dynamic>? 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<WargaModel?> 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<DonaturModel?> 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<PetugasDesaModel?> 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<UserData?> 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<WargaModel>(
baseUser: baseUser,
roleData: wargaData,
);
}
break;
case 'donatur':
final donaturData = await getDonaturProfile(baseUser.id);
if (donaturData != null) {
return UserData<DonaturModel>(
baseUser: baseUser,
roleData: donaturData,
);
}
break;
case 'petugas_desa':
final petugasDesaData = await getPetugasDesaProfile(baseUser.id);
if (petugasDesaData != null) {
return UserData<PetugasDesaModel>(
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<int?> 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<List<Map<String, dynamic>>?> 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<int?> 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<int?> 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<int?> 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<Map<String, int>?> 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<List<Map<String, dynamic>>?> 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<List<Map<String, dynamic>>?> 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<List<Map<String, dynamic>>?> 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<List<Map<String, dynamic>>?> 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<List<Map<String, dynamic>>?> 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<void> 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<void> 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<void> 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<void> 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<void> batchUpdateJadwalStatus(
Map<String, String> 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<Map<String, dynamic>> 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<String> successIds =
List<String>.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<String> errorIds =
List<String>.from(result['errors']['ids'] ?? []);
final List<String> errorMessages =
List<String>.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<void> _fallbackToIndividualUpdates(
Map<String, String> 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<List<Map<String, dynamic>>?> 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<Map<String, dynamic>?> 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<List<Map<String, dynamic>>?> getBentukBantuan() async {
try {
final response = await client.from('bentuk_bantuan').select('*');
return response;
} catch (e) {
print('Error getting bentuk bantuan: $e');
return null;
}
}
Future<List<Map<String, dynamic>>?> getKategoriBantuan() async {
try {
final response = await client.from('kategori_bantuan').select('*');
return response;
} catch (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');
throw e.toString();
}
}
Future<void> updateStok(String stokId, Map<String, dynamic> stok) async {
try {
await client.from('stok_bantuan').update(stok).eq('id', stokId);
} catch (e) {
print('Error updating stok: $e');
throw e.toString();
}
}
Future<void> 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<List<Map<String, dynamic>>?> 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<int?> 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<List<Map<String, dynamic>>?> 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<String?> 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<List<String>?> uploadMultipleFiles(
List<String> filePaths, String bucket, String folder) async {
try {
final List<String> 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<void> 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<void> 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<void> tambahPenitipanBantuan({
required String stokBantuanId,
required double jumlah,
required String deskripsi,
required List<String> 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<Map<String, dynamic>?> 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<List<Map<String, dynamic>>?> 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<List<Map<String, dynamic>>?> 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<String?> tambahDonatur(Map<String, dynamic> 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<List<Map<String, dynamic>>?> 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<List<Map<String, dynamic>>?>
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<List<Map<String, dynamic>>?> 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<void> 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<void> tambahTindakanPengaduan(Map<String, dynamic> tindakan) async {
try {
await client.from('tindakan_pengaduan').insert(tindakan);
} catch (e) {
print('Error adding tindakan pengaduan: $e');
throw e.toString();
}
}
Future<void> updateTindakanPengaduan(
String tindakanId, Map<String, dynamic> 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<void> 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<List<Map<String, dynamic>>?> 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<void> 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<void> 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<List<Map<String, dynamic>>?> 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<List<Map<String, dynamic>>?> 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<void> tambahPenerima(Map<String, dynamic> penerima) async {
try {
await client.from('warga').insert(penerima);
} catch (e) {
print('Error adding penerima: $e');
throw e.toString();
}
}
Future<void> updatePenerima(
String penerimaId, Map<String, dynamic> penerima) async {
try {
await client.from('warga').update(penerima).eq('id', penerimaId);
} catch (e) {
print('Error updating penerima: $e');
throw e.toString();
}
}
Future<void> 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<List<Map<String, dynamic>>?> 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<String?> generateLaporan(Map<String, dynamic> 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<String?> 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<void> 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<Map<String, dynamic>?> 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<void> 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<void> 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<void> updateDonaturProfile({
required String userId,
required String nama,
required String email,
String? noHp,
String? fotoProfil,
}) async {
try {
// Buat map untuk update data
final Map<String, dynamic> 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<void> 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<List<Map<String, dynamic>>> 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<void> 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<Map<String, dynamic>?> 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<List<Map<String, dynamic>>?> 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<List<LokasiPenyaluranModel>> 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<void> 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<List<Map<String, dynamic>>?> 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<bool> 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<String, dynamic> 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<List<Map<String, dynamic>>?> 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<bool> 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<Map<String, dynamic>?> 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<String, dynamic>) {
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<Map<String, dynamic>> tambahPenyaluran(
Map<String, dynamic> 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<List<Map<String, dynamic>>?> 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<void> updateWargaProfile({
required String userId,
required String namaLengkap,
required String email,
String? noHp,
String? fotoProfil,
}) async {
try {
// Buat map untuk update data
final Map<String, dynamic> 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<void> updatePetugasDesaProfile({
required String userId,
required String nama,
required String email,
String? noHp,
String? fotoProfil,
}) async {
try {
// Buat map untuk update data
final Map<String, dynamic> 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<void> 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<List<Map<String, dynamic>>?> 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<void> 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<void> 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<void> 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<void> 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<Map<String, dynamic>?> 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<Map<String, dynamic>?> 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;
}
}
}