429 lines
12 KiB
Dart
429 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:penyaluran_app/app/data/models/pengaduan_model.dart';
|
|
import 'package:penyaluran_app/app/data/models/tindakan_pengaduan_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';
|
|
import 'package:image_picker/image_picker.dart';
|
|
|
|
class PengaduanController extends GetxController {
|
|
final AuthController _authController = Get.find<AuthController>();
|
|
final SupabaseService _supabaseService = SupabaseService.to;
|
|
|
|
final RxBool isLoading = false.obs;
|
|
final RxBool isUploading = false.obs;
|
|
|
|
// Indeks kategori yang dipilih untuk filter
|
|
final RxInt selectedCategoryIndex = 0.obs;
|
|
|
|
// Data untuk pengaduan
|
|
final RxList<PengaduanModel> daftarPengaduan = <PengaduanModel>[].obs;
|
|
final RxInt jumlahDiproses = 0.obs;
|
|
final RxInt jumlahTindakan = 0.obs;
|
|
final RxInt jumlahSelesai = 0.obs;
|
|
|
|
// Controller untuk pencarian dan form
|
|
final TextEditingController searchController = TextEditingController();
|
|
final TextEditingController tindakanController = TextEditingController();
|
|
final TextEditingController catatanController = TextEditingController();
|
|
|
|
// Form key
|
|
final GlobalKey<FormState> tindakanFormKey = GlobalKey<FormState>();
|
|
|
|
// List untuk menyimpan path file bukti tindakan
|
|
final RxList<String> buktiTindakanPaths = <String>[].obs;
|
|
|
|
// Image picker
|
|
final ImagePicker _imagePicker = ImagePicker();
|
|
|
|
UserModel? get user => _authController.user;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
loadPengaduanData();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
searchController.dispose();
|
|
tindakanController.dispose();
|
|
catatanController.dispose();
|
|
super.onClose();
|
|
}
|
|
|
|
Future<void> loadPengaduanData() async {
|
|
isLoading.value = true;
|
|
try {
|
|
final pengaduanData =
|
|
await _supabaseService.getPengaduanWithPenerimaPenyaluran();
|
|
if (pengaduanData != null) {
|
|
daftarPengaduan.value =
|
|
pengaduanData.map((data) => PengaduanModel.fromJson(data)).toList();
|
|
|
|
// Hitung jumlah berdasarkan status
|
|
jumlahDiproses.value =
|
|
daftarPengaduan.where((item) => item.status == 'MENUNGGU').length;
|
|
jumlahTindakan.value =
|
|
daftarPengaduan.where((item) => item.status == 'TINDAKAN').length;
|
|
jumlahSelesai.value =
|
|
daftarPengaduan.where((item) => item.status == 'SELESAI').length;
|
|
}
|
|
} catch (e) {
|
|
print('Error loading pengaduan data: $e');
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> prosesPengaduan(String pengaduanId) async {
|
|
isLoading.value = true;
|
|
try {
|
|
await _supabaseService.prosesPengaduan(pengaduanId);
|
|
await loadPengaduanData();
|
|
Get.snackbar(
|
|
'Sukses',
|
|
'Pengaduan berhasil diproses',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.green,
|
|
colorText: Colors.white,
|
|
);
|
|
} catch (e) {
|
|
print('Error processing pengaduan: $e');
|
|
Get.snackbar(
|
|
'Error',
|
|
'Gagal memproses pengaduan: ${e.toString()}',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> tambahTindakanPengaduan({
|
|
required String pengaduanId,
|
|
required String tindakan,
|
|
required String kategoriTindakan,
|
|
required String statusTindakan,
|
|
required String prioritas,
|
|
String? catatan,
|
|
String? hasilTindakan,
|
|
required List<String> buktiTindakanPaths,
|
|
}) async {
|
|
try {
|
|
isLoading.value = true;
|
|
|
|
// Upload bukti tindakan jika ada
|
|
List<String> buktiTindakanUrls = [];
|
|
if (buktiTindakanPaths.isNotEmpty) {
|
|
for (var path in buktiTindakanPaths) {
|
|
final String? fileUrl = await SupabaseService.to
|
|
.uploadFile(path, 'tindakan_pengaduan', 'bukti_tindakan');
|
|
if (fileUrl != null) {
|
|
buktiTindakanUrls.add(fileUrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Buat objek tindakan
|
|
final Map<String, dynamic> tindakanData = {
|
|
'pengaduan_id': pengaduanId,
|
|
'tindakan': tindakan,
|
|
'catatan': catatan,
|
|
'status_tindakan': statusTindakan,
|
|
'prioritas': prioritas,
|
|
'kategori_tindakan': kategoriTindakan,
|
|
'hasil_tindakan': hasilTindakan,
|
|
'tanggal_tindakan': DateTime.now().toIso8601String(),
|
|
'petugas_id': user?.id,
|
|
'bukti_tindakan': buktiTindakanUrls,
|
|
'created_at': DateTime.now().toIso8601String(),
|
|
'updated_at': DateTime.now().toIso8601String(),
|
|
};
|
|
|
|
// Simpan tindakan ke Supabase
|
|
await SupabaseService.to.tambahTindakanPengaduan(tindakanData);
|
|
|
|
// Update status pengaduan jika perlu
|
|
if (statusTindakan == 'SELESAI') {
|
|
await SupabaseService.to.updateStatusPengaduan(pengaduanId, 'SELESAI');
|
|
} else {
|
|
await SupabaseService.to.updateStatusPengaduan(pengaduanId, 'TINDAKAN');
|
|
}
|
|
|
|
// Reset paths setelah berhasil
|
|
buktiTindakanPaths.clear();
|
|
|
|
//refresh page
|
|
Get.forceAppUpdate();
|
|
Get.back(); // Tutup dialog
|
|
|
|
Get.snackbar(
|
|
'Berhasil',
|
|
'Tindakan berhasil ditambahkan',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.green,
|
|
colorText: Colors.white,
|
|
);
|
|
} catch (e) {
|
|
print('Error adding tindakan: $e');
|
|
Get.snackbar(
|
|
'Error',
|
|
'Gagal menambahkan tindakan: ${e.toString()}',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> updateTindakanPengaduan({
|
|
required String tindakanId,
|
|
required String pengaduanId,
|
|
required String tindakan,
|
|
required String kategoriTindakan,
|
|
required String statusTindakan,
|
|
required String prioritas,
|
|
String? catatan,
|
|
String? hasilTindakan,
|
|
required List<String> buktiTindakanPaths,
|
|
}) async {
|
|
try {
|
|
isLoading.value = true;
|
|
|
|
// Upload bukti tindakan jika ada file baru (yang belum diupload)
|
|
List<String> buktiTindakanUrls = [];
|
|
for (var path in buktiTindakanPaths) {
|
|
// Jika path sudah berupa URL, tambahkan langsung
|
|
if (path.startsWith('http')) {
|
|
buktiTindakanUrls.add(path);
|
|
} else {
|
|
// Jika path adalah file lokal, upload dulu
|
|
final String? fileUrl = await SupabaseService.to
|
|
.uploadFile(path, 'tindakan_pengaduan', 'bukti_tindakan');
|
|
if (fileUrl != null) {
|
|
buktiTindakanUrls.add(fileUrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Buat objek tindakan
|
|
final Map<String, dynamic> tindakanData = {
|
|
'tindakan': tindakan,
|
|
'catatan': catatan,
|
|
'status_tindakan': statusTindakan,
|
|
'prioritas': prioritas,
|
|
'kategori_tindakan': kategoriTindakan,
|
|
'hasil_tindakan': hasilTindakan,
|
|
'bukti_tindakan': buktiTindakanUrls,
|
|
'updated_at': DateTime.now().toIso8601String(),
|
|
};
|
|
|
|
// Update tindakan di Supabase
|
|
await SupabaseService.to
|
|
.updateTindakanPengaduan(tindakanId, tindakanData);
|
|
|
|
// Reset paths setelah berhasil
|
|
buktiTindakanPaths.clear();
|
|
|
|
//refresh page
|
|
Get.forceAppUpdate();
|
|
Get.back(); // Tutup dialog
|
|
Get.snackbar(
|
|
'Berhasil',
|
|
'Tindakan berhasil diperbarui',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.green,
|
|
colorText: Colors.white,
|
|
);
|
|
} catch (e) {
|
|
print('Error updating tindakan: $e');
|
|
Get.snackbar(
|
|
'Error',
|
|
'Gagal memperbarui tindakan: ${e.toString()}',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> selesaikanPengaduan(String pengaduanId) async {
|
|
isLoading.value = true;
|
|
try {
|
|
await _supabaseService.updateStatusPengaduan(pengaduanId, 'SELESAI');
|
|
await loadPengaduanData();
|
|
Get.snackbar(
|
|
'Sukses',
|
|
'Pengaduan berhasil diselesaikan',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.green,
|
|
colorText: Colors.white,
|
|
);
|
|
} catch (e) {
|
|
print('Error completing pengaduan: $e');
|
|
Get.snackbar(
|
|
'Error',
|
|
'Gagal menyelesaikan pengaduan: ${e.toString()}',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> updateStatusPengaduan(String pengaduanId, String status) async {
|
|
isLoading.value = true;
|
|
try {
|
|
await _supabaseService.updateStatusPengaduan(pengaduanId, status);
|
|
await loadPengaduanData();
|
|
} catch (e) {
|
|
print('Error updating pengaduan status: $e');
|
|
Get.snackbar(
|
|
'Error',
|
|
'Gagal mengubah status pengaduan: ${e.toString()}',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<List<TindakanPengaduanModel>> getTindakanPengaduan(
|
|
String pengaduanId) async {
|
|
try {
|
|
final tindakanData =
|
|
await _supabaseService.getTindakanPengaduan(pengaduanId);
|
|
if (tindakanData != null) {
|
|
return tindakanData
|
|
.map((data) => TindakanPengaduanModel.fromJson(data))
|
|
.toList();
|
|
}
|
|
return [];
|
|
} catch (e) {
|
|
print('Error getting tindakan pengaduan: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<void> refreshData() async {
|
|
isLoading.value = true;
|
|
try {
|
|
await loadPengaduanData();
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
void changeCategory(int index) {
|
|
selectedCategoryIndex.value = index;
|
|
}
|
|
|
|
List<PengaduanModel> getFilteredPengaduan() {
|
|
switch (selectedCategoryIndex.value) {
|
|
case 0:
|
|
return daftarPengaduan;
|
|
case 1:
|
|
return daftarPengaduan
|
|
.where((item) => item.status == 'MENUNGGU')
|
|
.toList();
|
|
case 2:
|
|
return daftarPengaduan
|
|
.where((item) => item.status == 'TINDAKAN')
|
|
.toList();
|
|
case 3:
|
|
return daftarPengaduan
|
|
.where((item) => item.status == 'SELESAI')
|
|
.toList();
|
|
default:
|
|
return daftarPengaduan;
|
|
}
|
|
}
|
|
|
|
// Validasi form
|
|
String? validateTindakan(String? value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Tindakan tidak boleh kosong';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<Map<String, dynamic>> getDetailPengaduan(String pengaduanId) async {
|
|
try {
|
|
// Ambil data pengaduan
|
|
final pengaduanData =
|
|
await _supabaseService.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('id', pengaduanId).single();
|
|
|
|
// Ambil data tindakan pengaduan
|
|
final tindakanData =
|
|
await _supabaseService.getTindakanPengaduan(pengaduanId);
|
|
print(tindakanData);
|
|
|
|
// Gabungkan data
|
|
final result = {
|
|
'pengaduan': pengaduanData,
|
|
'tindakan': tindakanData ?? [],
|
|
};
|
|
|
|
return result;
|
|
} catch (e) {
|
|
print('Error getting detail pengaduan: $e');
|
|
return {
|
|
'pengaduan': null,
|
|
'tindakan': [],
|
|
};
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk memilih bukti tindakan
|
|
Future<void> pickBuktiTindakan({bool fromCamera = true}) async {
|
|
try {
|
|
final pickedFile = await _imagePicker.pickImage(
|
|
source: fromCamera ? ImageSource.camera : ImageSource.gallery,
|
|
imageQuality: 70,
|
|
maxWidth: 1000,
|
|
);
|
|
|
|
if (pickedFile != null) {
|
|
buktiTindakanPaths.add(pickedFile.path);
|
|
}
|
|
} catch (e) {
|
|
print('Error picking image: $e');
|
|
Get.snackbar(
|
|
'Error',
|
|
'Gagal mengambil gambar: ${e.toString()}',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk menghapus bukti tindakan
|
|
void removeBuktiTindakan(int index) {
|
|
if (index >= 0 && index < buktiTindakanPaths.length) {
|
|
buktiTindakanPaths.removeAt(index);
|
|
}
|
|
}
|
|
}
|