Perbarui model PenyaluranBantuan dan tampilan terkait untuk mendukung kategori bantuan dan informasi tambahan

- Ganti properti 'judul' menjadi 'nama' di model PenyaluranBantuanModel
- Tambahkan properti baru: kategoriBantuanId, tanggalPermintaan, jumlahPenerima, dan skemaId
- Perbarui tampilan JadwalSectionWidget dan PermintaanPenjadwalanSummaryWidget untuk menggunakan PenyaluranBantuanModel
- Tambahkan format tanggal dan waktu di tampilan
- Perbarui controller untuk memuat data lokasi penyaluran dan kategori bantuan
This commit is contained in:
Khafidh Fuadi
2025-03-13 19:56:17 +07:00
parent d9cc7aaf92
commit 0223d457a5
8 changed files with 243 additions and 78 deletions

View File

@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
class JadwalSectionWidget extends StatelessWidget {
final JadwalPenyaluranController controller;
final String title;
final List<dynamic> jadwalList;
final List<PenyaluranBantuanModel> jadwalList;
final String status;
const JadwalSectionWidget({
@ -62,7 +64,7 @@ class JadwalSectionWidget extends StatelessWidget {
);
}
List<dynamic> _getCurrentJadwalList() {
List<PenyaluranBantuanModel> _getCurrentJadwalList() {
switch (title) {
case 'Hari Ini':
return controller.jadwalHariIni.toList();
@ -75,11 +77,7 @@ class JadwalSectionWidget extends StatelessWidget {
}
}
Widget _buildJadwalItem(TextTheme textTheme, dynamic jadwal) {
// Konversi jadwal ke Map jika itu adalah PenyaluranBantuanModel
final Map<String, dynamic> jadwalData =
jadwal is Map<String, dynamic> ? jadwal : jadwal.toJson();
Widget _buildJadwalItem(TextTheme textTheme, PenyaluranBantuanModel jadwal) {
Color statusColor;
switch (status) {
case 'Aktif':
@ -95,10 +93,26 @@ class JadwalSectionWidget extends StatelessWidget {
statusColor = Colors.orange;
}
// Format tanggal
String formattedDate = jadwal.tanggalPenyaluran != null
? DateFormat('dd MMMM yyyy').format(jadwal.tanggalPenyaluran!)
: 'Belum ditentukan';
// Format waktu
String formattedTime = jadwal.tanggalPenyaluran != null
? DateFormat('HH:mm').format(jadwal.tanggalPenyaluran!)
: '-';
// Dapatkan nama lokasi dan kategori
String lokasiName =
controller.getLokasiPenyaluranName(jadwal.lokasiPenyaluranId);
String kategoriName =
controller.getKategoriBantuanName(jadwal.kategoriBantuanId);
return GestureDetector(
onTap: () {
// Navigasi ke halaman pelaksanaan penyaluran dengan data jadwal
Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwalData);
Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwal);
},
child: Container(
width: double.infinity,
@ -123,10 +137,13 @@ class JadwalSectionWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
jadwalData['lokasi'] ?? '',
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
Expanded(
child: Text(
jadwal.nama ?? 'Tanpa Nama',
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
),
Container(
@ -147,24 +164,38 @@ class JadwalSectionWidget extends StatelessWidget {
],
),
const SizedBox(height: 8),
if (jadwal.deskripsi != null && jadwal.deskripsi!.isNotEmpty) ...[
Text(
jadwal.deskripsi!,
style: textTheme.bodyMedium,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
],
Text(
'Kategori Bantuan: ${jadwalData['kategori_bantuan'] ?? ''}',
'Lokasi: $lokasiName',
style: textTheme.bodyMedium,
),
const SizedBox(height: 4),
Text(
'Tanggal: ${jadwalData['tanggal'] ?? ''}',
'Kategori: $kategoriName',
style: textTheme.bodyMedium,
),
const SizedBox(height: 4),
Text(
'Waktu: ${jadwalData['waktu'] ?? ''}',
'Tanggal: $formattedDate',
style: textTheme.bodyMedium,
),
if (jadwalData['jumlah_penerima'] != null) ...[
const SizedBox(height: 4),
Text(
'Waktu: $formattedTime',
style: textTheme.bodyMedium,
),
if (jadwal.jumlahPenerima != null) ...[
const SizedBox(height: 4),
Text(
'Jumlah Penerima: ${jadwalData['jumlah_penerima']}',
'Jumlah Penerima: ${jadwal.jumlahPenerima}',
style: textTheme.bodyMedium,
),
],

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart';
@ -134,10 +136,16 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
});
}
Widget _buildPermintaanPreview(TextTheme textTheme, dynamic permintaan) {
// Konversi permintaan ke Map jika itu adalah PenyaluranBantuanModel
final Map<String, dynamic> permintaanData =
permintaan is Map<String, dynamic> ? permintaan : permintaan.toJson();
Widget _buildPermintaanPreview(
TextTheme textTheme, PenyaluranBantuanModel permintaan) {
// Format tanggal
String formattedDate = permintaan.tanggalPermintaan != null
? DateFormat('dd MMMM yyyy').format(permintaan.tanggalPermintaan!)
: 'Belum ditentukan';
// Dapatkan nama kategori
String kategoriName =
controller.getKategoriBantuanName(permintaan.kategoriBantuanId);
return Container(
width: double.infinity,
@ -155,7 +163,7 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
children: [
Expanded(
child: Text(
permintaanData['nama'] ?? '',
permintaan.nama ?? 'Tanpa Nama',
style: textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
@ -180,13 +188,20 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
],
),
const SizedBox(height: 4),
if (permintaan.deskripsi != null && permintaan.deskripsi!.isNotEmpty)
Text(
permintaan.deskripsi!,
style: textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Text(
'Kategori: ${permintaanData['kategori_bantuan'] ?? ''}',
'Kategori: $kategoriName',
style: textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
),
Text(
'Tanggal: ${permintaanData['tanggal_permintaan'] ?? ''}',
'Tanggal: $formattedDate',
style: textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
),

View File

@ -120,7 +120,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
permintaan.judul ?? '',
permintaan.nama ?? '',
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
@ -149,7 +149,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
'Jenis Bantuan: ${permintaan.judul ?? ''}',
'Jenis Bantuan: ${permintaan.kategoriBantuanId ?? ''}',
style: textTheme.bodyMedium,
),
const SizedBox(height: 4),
@ -200,7 +200,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
return DropdownMenuItem<String>(
value: jadwal.id,
child: Text(
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.judul ?? ''})'),
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.nama ?? ''})'),
);
}).toList();
@ -220,7 +220,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan.judul}.'),
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan.nama}.'),
const SizedBox(height: 16),
const Text('Pilih jadwal penyaluran:'),
const SizedBox(height: 8),
@ -290,7 +290,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Anda akan menolak permintaan penjadwalan dari ${permintaan.judul}.'),
'Anda akan menolak permintaan penjadwalan dari ${permintaan.nama}.'),
const SizedBox(height: 16),
const Text('Alasan penolakan:'),
const SizedBox(height: 8),

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
import 'package:penyaluran_app/app/data/models/lokasi_penyaluran_model.dart';
import 'package:penyaluran_app/app/data/models/kategori_bantuan_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';
@ -27,6 +29,12 @@ class JadwalPenyaluranController extends GetxController {
<PenyaluranBantuanModel>[].obs;
final RxInt jumlahPermintaanPenjadwalan = 0.obs;
// Cache untuk lokasi penyaluran dan kategori bantuan
final RxMap<String, LokasiPenyaluranModel> lokasiPenyaluranCache =
<String, LokasiPenyaluranModel>{}.obs;
final RxMap<String, KategoriBantuanModel> kategoriBantuanCache =
<String, KategoriBantuanModel>{}.obs;
// Controller untuk pencarian
final TextEditingController searchController = TextEditingController();
@ -37,6 +45,8 @@ class JadwalPenyaluranController extends GetxController {
super.onInit();
loadJadwalData();
loadPermintaanPenjadwalanData();
loadLokasiPenyaluranData();
loadKategoriBantuanData();
}
@override
@ -92,6 +102,50 @@ class JadwalPenyaluranController extends GetxController {
}
}
Future<void> loadLokasiPenyaluranData() async {
try {
final lokasiData = await _supabaseService.getAllLokasiPenyaluran();
if (lokasiData != null) {
for (var lokasi in lokasiData) {
final lokasiModel = LokasiPenyaluranModel.fromJson(lokasi);
lokasiPenyaluranCache[lokasiModel.id] = lokasiModel;
}
}
} catch (e) {
print('Error loading lokasi penyaluran data: $e');
}
}
Future<void> loadKategoriBantuanData() async {
try {
final kategoriData = await _supabaseService.getAllKategoriBantuan();
if (kategoriData != null) {
for (var kategori in kategoriData) {
final kategoriModel = KategoriBantuanModel.fromJson(kategori);
if (kategoriModel.id != null) {
kategoriBantuanCache[kategoriModel.id!] = kategoriModel;
}
}
}
} catch (e) {
print('Error loading kategori bantuan data: $e');
}
}
// Mendapatkan nama lokasi penyaluran berdasarkan ID
String getLokasiPenyaluranName(String? lokasiId) {
if (lokasiId == null) return 'Lokasi tidak diketahui';
final lokasi = lokasiPenyaluranCache[lokasiId];
return lokasi?.nama ?? 'Lokasi tidak diketahui';
}
// Mendapatkan nama kategori bantuan berdasarkan ID
String getKategoriBantuanName(String? kategoriId) {
if (kategoriId == null) return 'Kategori tidak diketahui';
final kategori = kategoriBantuanCache[kategoriId];
return kategori?.nama ?? 'Kategori tidak diketahui';
}
Future<void> approveJadwal(String jadwalId) async {
isLoading.value = true;
try {
@ -176,6 +230,8 @@ class JadwalPenyaluranController extends GetxController {
try {
await loadJadwalData();
await loadPermintaanPenjadwalanData();
await loadLokasiPenyaluranData();
await loadKategoriBantuanData();
} finally {
isLoading.value = false;
}

View File

@ -10,50 +10,65 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Ringkasan jadwal
_buildJadwalSummary(context),
return RefreshIndicator(
onRefresh: () => controller.refreshData(),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Obx(() {
if (controller.isLoading.value) {
return const Center(
child: Padding(
padding: EdgeInsets.all(32.0),
child: CircularProgressIndicator(),
),
);
}
const SizedBox(height: 20),
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Ringkasan jadwal
_buildJadwalSummary(context),
// Ringkasan Permintaan Penjadwalan
PermintaanPenjadwalanSummaryWidget(controller: controller),
const SizedBox(height: 20),
const SizedBox(height: 20),
// Ringkasan Permintaan Penjadwalan
PermintaanPenjadwalanSummaryWidget(controller: controller),
// Jadwal hari ini
JadwalSectionWidget(
controller: controller,
title: 'Hari Ini',
jadwalList: controller.jadwalHariIni,
status: 'Aktif',
),
const SizedBox(height: 20),
const SizedBox(height: 20),
// Jadwal hari ini
JadwalSectionWidget(
controller: controller,
title: 'Hari Ini',
jadwalList: controller.jadwalHariIni,
status: 'Aktif',
),
// Jadwal mendatang
JadwalSectionWidget(
controller: controller,
title: 'Mendatang',
jadwalList: controller.jadwalMendatang,
status: 'Terjadwal',
),
const SizedBox(height: 20),
const SizedBox(height: 20),
// Jadwal mendatang
JadwalSectionWidget(
controller: controller,
title: 'Mendatang',
jadwalList: controller.jadwalMendatang,
status: 'Terjadwal',
),
// Jadwal selesai
JadwalSectionWidget(
controller: controller,
title: 'Selesai',
jadwalList: controller.jadwalSelesai,
status: 'Selesai',
),
],
const SizedBox(height: 20),
// Jadwal selesai
JadwalSectionWidget(
controller: controller,
title: 'Selesai',
jadwalList: controller.jadwalSelesai,
status: 'Selesai',
),
],
);
}),
),
),
);

View File

@ -237,7 +237,8 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
);
}
Widget _buildPermintaanItem(BuildContext context, PenyaluranBantuanModel item) {
Widget _buildPermintaanItem(
BuildContext context, PenyaluranBantuanModel item) {
Color statusColor = Colors.orange;
IconData statusIcon = Icons.pending_actions;
@ -266,7 +267,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
children: [
Expanded(
child: Text(
item.judul ?? '',
item.nama ?? '',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
@ -317,7 +318,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
context,
icon: Icons.category,
label: 'Jenis Bantuan',
value: item.judul ?? '',
value: item.nama ?? '',
),
),
],
@ -429,7 +430,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
return DropdownMenuItem<String>(
value: jadwal.id,
child: Text(
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.judul ?? ''})'),
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.nama ?? ''})'),
);
}).toList();
@ -449,7 +450,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan.judul ?? 'Penerima'}.'),
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan.nama ?? 'Penerima'}.'),
const SizedBox(height: 16),
const Text('Pilih jadwal penyaluran:'),
const SizedBox(height: 8),
@ -519,7 +520,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Anda akan menolak permintaan penjadwalan dari ${permintaan.judul ?? 'Penerima'}.'),
'Anda akan menolak permintaan penjadwalan dari ${permintaan.nama ?? 'Penerima'}.'),
const SizedBox(height: 16),
const Text('Alasan penolakan:'),
const SizedBox(height: 8),