Perbarui dependensi dan tambahkan fungsionalitas laporan penyaluran. Tambahkan paket baru seperti file_picker, pdf, dan open_file ke dalam pubspec.yaml. Hapus model LaporanModel yang tidak digunakan dan ganti dengan LaporanPenyaluranModel. Modifikasi tampilan dan controller untuk mendukung pengelolaan laporan penyaluran, termasuk navigasi dan ekspor ke PDF. Perbarui rute aplikasi untuk mencakup halaman laporan penyaluran baru.

This commit is contained in:
Khafidh Fuadi
2025-03-20 05:19:04 +07:00
parent 3b12c7af86
commit 54c4660302
29 changed files with 4702 additions and 539 deletions

View File

@ -2,9 +2,11 @@ import 'package:get/get.dart';
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/skema_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/penerima_penyaluran_model.dart';
import 'package:penyaluran_app/app/data/models/laporan_penyaluran_model.dart';
import 'package:penyaluran_app/app/services/supabase_service.dart';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:penyaluran_app/app/modules/laporan_penyaluran/controllers/laporan_penyaluran_controller.dart';
class DetailPenyaluranController extends GetxController {
final SupabaseService _supabaseService = Get.find<SupabaseService>();
@ -14,10 +16,16 @@ class DetailPenyaluranController extends GetxController {
final penyaluran = Rx<PenyaluranBantuanModel?>(null);
final skemaBantuan = Rx<SkemaBantuanModel?>(null);
final penerimaPenyaluran = <PenerimaPenyaluranModel>[].obs;
final laporan = Rx<LaporanPenyaluranModel?>(null);
final isLoadingLaporan = false.obs;
// Status untuk mengetahui apakah petugas desa
final isPetugasDesa = false.obs;
// Tambahkan referensi ke controller laporan
LaporanPenyaluranController? laporanController;
final RxBool isExporting = false.obs;
@override
void onInit() {
super.onInit();
@ -432,6 +440,9 @@ class DetailPenyaluranController extends GetxController {
}
penerimaPenyaluran.assignAll(penerima);
// Periksa apakah ada laporan untuk penyaluran ini
await checkLaporanPenyaluran(penyaluranId);
// if (penerima.isNotEmpty) {
// print('DetailPenyaluranController - ID penerima: ${penerima[0].id}');
// }
@ -446,6 +457,57 @@ class DetailPenyaluranController extends GetxController {
}
}
Future<void> checkLaporanPenyaluran(String penyaluranId) async {
try {
isLoadingLaporan.value = true;
final response = await _supabaseService.client
.from('laporan_penyaluran')
.select('*')
.eq('penyaluran_bantuan_id', penyaluranId)
.maybeSingle();
if (response != null) {
// Laporan ditemukan
laporan.value = LaporanPenyaluranModel.fromJson(response);
} else {
// Tidak ada laporan
laporan.value = null;
}
} catch (e) {
print('Error saat memeriksa laporan penyaluran: $e');
laporan.value = null;
} finally {
isLoadingLaporan.value = false;
}
}
void navigateToLaporanCreate() {
if (penyaluran.value?.id == null) return;
// Kirim ID penyaluran langsung sebagai argument (String), bukan dalam bentuk Map
Get.toNamed('/laporan-penyaluran/create', arguments: penyaluran.value!.id)
?.then((value) {
if (value == true) {
// Refresh data setelah membuat laporan
checkLaporanPenyaluran(penyaluran.value!.id!);
}
});
}
void navigateToLaporanDetail() {
if (laporan.value?.id == null) return;
// Navigasi ke halaman detail dengan mengirimkan ID sebagai argument
Get.toNamed('/laporan-penyaluran/detail', arguments: laporan.value!.id)
?.then((value) {
if (value == true) {
// Refresh data setelah melihat detail laporan
checkLaporanPenyaluran(penyaluran.value!.id!);
}
});
}
// Metode untuk verifikasi penerima berdasarkan QR code
Future<bool> verifikasiPenerimaByQrCode(
String penyaluranId, String qrHash) async {
@ -498,4 +560,53 @@ class DetailPenyaluranController extends GetxController {
isProcessing.value = false;
}
}
// Method untuk memuat controller laporan
Future<void> loadLaporanPenyaluranController() async {
if (laporanController == null) {
// Cek apakah controller sudah ada di Get
if (Get.isRegistered<LaporanPenyaluranController>()) {
laporanController = Get.find<LaporanPenyaluranController>();
} else {
// Jika belum ada, buat instance baru
laporanController = Get.put(LaporanPenyaluranController());
}
}
// Pastikan data laporan dimuat
if (laporan.value != null && penyaluran.value != null) {
await laporanController!.fetchLaporanDetail(laporan.value!.id!);
}
}
// Method untuk export PDF
Future<void> exportToPdf() async {
if (laporan.value == null || penyaluran.value == null) {
Get.snackbar(
'Error',
'Data laporan atau penyaluran tidak tersedia',
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
);
return;
}
isExporting.value = true;
try {
await loadLaporanPenyaluranController();
await laporanController!.exportToPdf(laporan.value!, penyaluran.value!);
} catch (e) {
print('Error saat export PDF: $e');
Get.snackbar(
'Error',
'Gagal mengekspor laporan ke PDF',
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
);
} finally {
isExporting.value = false;
}
}
}

View File

@ -1,197 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:penyaluran_app/app/data/models/laporan_model.dart';
import 'package:penyaluran_app/app/data/models/user_model.dart';
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
import 'package:penyaluran_app/app/services/supabase_service.dart';
class LaporanController extends GetxController {
final AuthController _authController = Get.find<AuthController>();
final SupabaseService _supabaseService = SupabaseService.to;
final RxBool isLoading = false.obs;
// Indeks kategori yang dipilih untuk filter
final RxInt selectedCategoryIndex = 0.obs;
// Data untuk laporan
final RxList<LaporanModel> daftarLaporan = <LaporanModel>[].obs;
// Filter tanggal
final Rx<DateTime?> tanggalMulai = Rx<DateTime?>(null);
final Rx<DateTime?> tanggalSelesai = Rx<DateTime?>(null);
// Controller untuk pencarian
final TextEditingController searchController = TextEditingController();
UserModel? get user => _authController.user;
@override
void onInit() {
super.onInit();
// Set default tanggal filter (1 bulan terakhir)
tanggalSelesai.value = DateTime.now();
tanggalMulai.value = DateTime.now().subtract(const Duration(days: 30));
loadLaporanData();
}
@override
void onClose() {
searchController.dispose();
super.onClose();
}
Future<void> loadLaporanData() async {
isLoading.value = true;
try {
final laporanData = await _supabaseService.getLaporan(
tanggalMulai.value,
tanggalSelesai.value,
);
if (laporanData != null) {
daftarLaporan.value =
laporanData.map((data) => LaporanModel.fromJson(data)).toList();
}
} catch (e) {
print('Error loading laporan data: $e');
} finally {
isLoading.value = false;
}
}
Future<void> generateLaporan(String jenis) async {
isLoading.value = true;
try {
final laporan = LaporanModel(
jenis: jenis,
tanggalMulai: tanggalMulai.value,
tanggalSelesai: tanggalSelesai.value,
petugasId: user?.id,
createdAt: DateTime.now(),
);
final laporanId =
await _supabaseService.generateLaporan(laporan.toJson());
if (laporanId != null) {
await loadLaporanData();
Get.snackbar(
'Sukses',
'Laporan berhasil dibuat',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
}
} catch (e) {
print('Error generating laporan: $e');
Get.snackbar(
'Error',
'Gagal membuat laporan: ${e.toString()}',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
}
}
Future<void> downloadLaporan(String laporanId) async {
isLoading.value = true;
try {
final url = await _supabaseService.downloadLaporan(laporanId);
if (url != null) {
// Implementasi download file
Get.snackbar(
'Sukses',
'Laporan berhasil diunduh',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
}
} catch (e) {
print('Error downloading laporan: $e');
Get.snackbar(
'Error',
'Gagal mengunduh laporan: ${e.toString()}',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
}
}
Future<void> deleteLaporan(String laporanId) async {
isLoading.value = true;
try {
await _supabaseService.deleteLaporan(laporanId);
await loadLaporanData();
Get.snackbar(
'Sukses',
'Laporan berhasil dihapus',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
} catch (e) {
print('Error deleting laporan: $e');
Get.snackbar(
'Error',
'Gagal menghapus laporan: ${e.toString()}',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
isLoading.value = false;
}
}
void setTanggalMulai(DateTime tanggal) {
tanggalMulai.value = tanggal;
}
void setTanggalSelesai(DateTime tanggal) {
tanggalSelesai.value = tanggal;
}
Future<void> applyFilter() async {
await loadLaporanData();
}
Future<void> refreshData() async {
isLoading.value = true;
try {
await loadLaporanData();
} finally {
isLoading.value = false;
}
}
void changeCategory(int index) {
selectedCategoryIndex.value = index;
}
List<LaporanModel> getFilteredLaporan() {
switch (selectedCategoryIndex.value) {
case 0:
return daftarLaporan;
case 1:
return daftarLaporan
.where((item) => item.jenis == 'PENYALURAN')
.toList();
case 2:
return daftarLaporan
.where((item) => item.jenis == 'STOK_BANTUAN')
.toList();
case 3:
return daftarLaporan.where((item) => item.jenis == 'PENERIMA').toList();
default:
return daftarLaporan;
}
}
}