Perbarui model PenitipanBantuan untuk menggunakan stok_bantuan sebagai kategori bantuan. Tambahkan rute baru untuk Donatur dengan beberapa halaman terkait, termasuk donaturDashboard, donaturSkema, donaturJadwal, donaturPenitipan, dan donaturLaporan. Modifikasi rute dan tampilan untuk meningkatkan navigasi dan pengalaman pengguna.

This commit is contained in:
Khafidh Fuadi
2025-03-26 08:33:55 +07:00
parent e881b2e37f
commit 88bef1c8e1
17 changed files with 4233 additions and 53 deletions

View File

@ -0,0 +1,457 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:penyaluran_app/app/data/models/donatur_model.dart';
import 'package:penyaluran_app/app/data/models/penitipan_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/skema_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/laporan_penyaluran_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/data/models/stok_bantuan_model.dart';
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
import 'package:penyaluran_app/app/services/supabase_service.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:supabase_flutter/supabase_flutter.dart';
class DonaturDashboardController extends GetxController {
final AuthController _authController = Get.find<AuthController>();
final SupabaseService _supabaseService = SupabaseService.to;
final Rx<BaseUserModel?> currentUser = Rx<BaseUserModel?>(null);
// Variabel untuk foto profil
final RxString fotoProfil = ''.obs;
// Indeks tab yang aktif di bottom navigation bar
final RxInt activeTabIndex = 0.obs;
// Data untuk skema bantuan tersedia
final RxList<SkemaBantuanModel> skemaBantuan = <SkemaBantuanModel>[].obs;
// Data untuk jadwal penyaluran
final RxList<PenyaluranBantuanModel> jadwalPenyaluran =
<PenyaluranBantuanModel>[].obs;
// Data untuk riwayat penitipan bantuan
final RxList<PenitipanBantuanModel> penitipanBantuan =
<PenitipanBantuanModel>[].obs;
// Data untuk laporan penyaluran
final RxList<LaporanPenyaluranModel> laporanPenyaluran =
<LaporanPenyaluranModel>[].obs;
// Data untuk stok bantuan yang tersedia
final RxList<StokBantuanModel> stokBantuan = <StokBantuanModel>[].obs;
// Indikator loading
final RxBool isLoading = false.obs;
// Jumlah notifikasi belum dibaca
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
// Data untuk foto bantuan pada form penitipan
final RxList<String> fotoBantuanPaths = <String>[].obs;
final ImagePicker _imagePicker = ImagePicker();
// Getter untuk data user
BaseUserModel? get user => _authController.baseUser;
String get role => user?.role ?? 'DONATUR';
String get nama {
// Gunakan namaLengkap dari roleData jika tersedia
if (_authController.isDonatur && _authController.roleData != null) {
return _authController.roleData.namaLengkap ??
_authController.displayName;
}
// Gunakan displayName dari AuthController
return _authController.displayName;
}
String? get desa => user?.desa?.nama;
// Getter untuk alamat dan noHp
String? get alamat {
if (_authController.isDonatur && _authController.roleData != null) {
return (_authController.roleData as DonaturModel).alamat;
}
return null;
}
String? get noHp {
if (_authController.isDonatur && _authController.roleData != null) {
return (_authController.roleData as DonaturModel).noHp;
}
return null;
}
// Getter untuk jenis donatur
String? get jenis {
if (_authController.isDonatur && _authController.roleData != null) {
return (_authController.roleData as DonaturModel).jenis;
}
return null;
}
// Getter untuk foto profil
String? get profilePhotoUrl {
// 1. Coba ambil dari fotoProfil yang sudah disimpan
if (fotoProfil.isNotEmpty) {
return fotoProfil.value;
}
// 2. Coba ambil dari roleData jika merupakan DonaturModel
if (_authController.isDonatur && _authController.roleData != null) {
final donaturData = _authController.roleData as DonaturModel;
if (donaturData.fotoProfil != null &&
donaturData.fotoProfil!.isNotEmpty) {
return donaturData.fotoProfil;
}
}
// 3. Coba ambil dari userData.roleData.fotoProfil
final userData = _authController.userData;
if (userData != null && userData.roleData is DonaturModel) {
final donaturData = userData.roleData as DonaturModel;
if (donaturData.fotoProfil != null &&
donaturData.fotoProfil!.isNotEmpty) {
return donaturData.fotoProfil;
}
}
return null;
}
@override
void onInit() {
super.onInit();
fetchData();
loadUserData();
}
@override
void onReady() {
super.onReady();
// Perbarui data user dan foto profil saat halaman siap
loadUserData();
}
void loadUserData() {
currentUser.value = _authController.baseUser;
if (_authController.userData != null) {
if (_authController.isDonatur) {
var donaturData = _authController.roleData;
// Ambil foto profil dari donaturData jika ada
if (donaturData != null &&
donaturData.fotoProfil != null &&
donaturData.fotoProfil!.isNotEmpty) {
fotoProfil.value = donaturData.fotoProfil!;
}
}
}
// Ambil foto profil dari database
_fetchProfilePhoto();
}
// Metode untuk mengambil foto profil
Future<void> _fetchProfilePhoto() async {
try {
if (user?.id == null) return;
final donaturData = await _supabaseService.client
.from('donatur')
.select('foto_profil')
.eq('id', user!.id)
.maybeSingle();
if (donaturData != null && donaturData['foto_profil'] != null) {
fotoProfil.value = donaturData['foto_profil'];
}
} catch (e) {
print('Error fetching profile photo: $e');
}
}
void fetchData() async {
isLoading.value = true;
try {
// Pastikan user sudah login dan memiliki ID
if (user?.id == null) {
throw Exception('User tidak terautentikasi');
}
// Ambil data skema bantuan
await fetchSkemaBantuan();
// Ambil data jadwal penyaluran
await fetchJadwalPenyaluran();
// Ambil data penitipan bantuan
await fetchPenitipanBantuan();
// Ambil data laporan penyaluran
await fetchLaporanPenyaluran();
// Ambil data stok bantuan
await fetchStokBantuan();
// Ambil data notifikasi
await fetchNotifikasi();
} catch (e) {
print('Error fetching data: $e');
} finally {
isLoading.value = false;
}
}
// Ambil data skema bantuan
Future<void> fetchSkemaBantuan() async {
try {
final response = await _supabaseService.client
.from('xx02_skema_bantuan')
.select()
.order('created_at', ascending: false);
skemaBantuan.value = response
.map((data) => SkemaBantuanModel.fromJson(data))
.toList()
.cast<SkemaBantuanModel>();
} catch (e) {
print('Error fetching skema bantuan: $e');
}
}
// Ambil data jadwal penyaluran
Future<void> fetchJadwalPenyaluran() async {
try {
final now = DateTime.now();
final response = await _supabaseService.client
.from('penyaluran_bantuan')
.select()
.order('tanggal_penyaluran', ascending: true);
// Konversi ke model lalu filter di sisi client
final allJadwal = response
.map((data) => PenyaluranBantuanModel.fromJson(data))
.toList()
.cast<PenyaluranBantuanModel>();
// Filter jadwal yang tanggalnya lebih besar dari hari ini
jadwalPenyaluran.value = allJadwal
.where((jadwal) =>
jadwal.tanggalPenyaluran != null &&
jadwal.tanggalPenyaluran!.isAfter(now))
.toList();
} catch (e) {
print('Error fetching jadwal penyaluran: $e');
}
}
// Ambil data penitipan bantuan
Future<void> fetchPenitipanBantuan() async {
try {
if (user?.id == null) return;
final response = await _supabaseService.client
.from('penitipan_bantuan')
.select('*, donatur(*), stok_bantuan:stok_bantuan_id(*)')
.eq('donatur_id', user!.id)
.order('created_at', ascending: false);
penitipanBantuan.value = response
.map((data) => PenitipanBantuanModel.fromJson(data))
.toList()
.cast<PenitipanBantuanModel>();
} catch (e) {
print('Error fetching penitipan bantuan: $e');
}
}
// Ambil data laporan penyaluran
Future<void> fetchLaporanPenyaluran() async {
try {
final response = await _supabaseService.client
.from('laporan_penyaluran')
.select()
.order('created_at', ascending: false);
laporanPenyaluran.value = response
.map((data) => LaporanPenyaluranModel.fromJson(data))
.toList()
.cast<LaporanPenyaluranModel>();
} catch (e) {
print('Error fetching laporan penyaluran: $e');
}
}
// Ambil data stok bantuan
Future<void> fetchStokBantuan() async {
try {
final response = await _supabaseService.client
.from('stok_bantuan')
.select('*, kategori_bantuan:kategori_bantuan_id(*)')
.order('nama', ascending: true);
stokBantuan.value = response
.map((data) => StokBantuanModel.fromJson(data))
.toList()
.cast<StokBantuanModel>();
} catch (e) {
print('Error fetching stok bantuan: $e');
}
}
// Ambil data notifikasi
Future<void> fetchNotifikasi() async {
try {
if (user?.id == null) return;
final response = await _supabaseService.client
.from('notifikasi')
.select('*')
.eq('user_id', user!.id)
.eq('is_read', false)
.count();
jumlahNotifikasiBelumDibaca.value = response.count ?? 0;
} catch (e) {
print('Error fetching notifikasi: $e');
}
}
// Fungsi untuk logout
void logout() async {
try {
await _authController.logout();
Get.offAllNamed(Routes.login);
} catch (e) {
print('Error during logout: $e');
Get.snackbar(
'Error',
'Terjadi kesalahan saat logout: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
}
// Mendapatkan daftar stok bantuan yang tersedia
List<StokBantuanModel> getAvailableStokBantuan() {
// Filter stok bantuan yang jumlahnya lebih dari 0
return stokBantuan.where((stok) => (stok.totalStok ?? 0) > 0).toList();
}
// Ambil gambar dari kamera atau galeri
Future<void> pickImage({required bool isCamera}) async {
try {
final source = isCamera ? ImageSource.camera : ImageSource.gallery;
final pickedFile = await _imagePicker.pickImage(
source: source,
imageQuality: 70, // Kurangi kualitas untuk menghemat ukuran
);
if (pickedFile != null) {
fotoBantuanPaths.add(pickedFile.path);
}
} catch (e) {
print('Error picking image: $e');
Get.snackbar(
'Gagal',
'Terjadi kesalahan saat mengambil gambar',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
// Hapus foto bantuan dari daftar
void removeFotoBantuan(int index) {
if (index >= 0 && index < fotoBantuanPaths.length) {
fotoBantuanPaths.removeAt(index);
}
}
// Reset foto bantuan paths
void resetFotoBantuan() {
fotoBantuanPaths.clear();
}
// Membuat penitipan bantuan baru
Future<void> createPenitipanBantuan(
String? stokBantuanId,
double jumlah,
String deskripsi,
String? skemaBantuanId,
) async {
try {
isLoading.value = true;
if (user?.id == null) {
throw Exception('User tidak terautentikasi');
}
if (stokBantuanId == null) {
throw Exception('Stok bantuan harus dipilih');
}
if (fotoBantuanPaths.isEmpty) {
throw Exception('Foto bantuan harus diunggah');
}
// Unggah foto bantuan ke storage menggunakan metode dari SupabaseService
final fotoBantuanUrls = await _supabaseService.uploadMultipleFiles(
fotoBantuanPaths, 'penitipan', 'foto_bantuan');
// Data yang akan disimpan
final Map<String, dynamic> data = {
'donatur_id': user!.id,
'stok_bantuan_id': stokBantuanId,
'jumlah': jumlah,
'deskripsi': deskripsi,
'status': 'MENUNGGU',
'tanggal_penitipan': DateTime.now().toIso8601String(),
'foto_bantuan': fotoBantuanUrls,
};
// Tambahkan skema bantuan jika ada
if (skemaBantuanId != null && skemaBantuanId.isNotEmpty) {
data['skema_bantuan_id'] = skemaBantuanId;
}
// Simpan ke database
await _supabaseService.client.from('penitipan_bantuan').insert(data);
// Reset foto bantuan setelah berhasil disimpan
resetFotoBantuan();
// Ambil data penitipan bantuan yang baru
await fetchPenitipanBantuan();
// Tampilkan pesan sukses
Get.snackbar(
'Berhasil',
'Penitipan bantuan berhasil dikirim dan akan diproses oleh petugas desa',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
// Pindah ke tab riwayat penitipan
DefaultTabController.of(Get.context!)?.animateTo(0);
} catch (e) {
print('Error creating penitipan bantuan: $e');
Get.snackbar(
'Gagal',
'Terjadi kesalahan saat mengirim penitipan bantuan: $e',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
} finally {
isLoading.value = false;
}
}
}