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

@ -1,4 +1,5 @@
import 'package:get/get.dart';
import 'package:penyaluran_app/app/modules/laporan_penyaluran/controllers/laporan_penyaluran_controller.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
@ -6,7 +7,6 @@ import 'package:penyaluran_app/app/modules/petugas_desa/controllers/stok_bantuan
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/pengaduan_controller.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_bantuan_controller.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/laporan_controller.dart';
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/counter_service.dart';
@ -60,8 +60,8 @@ class PetugasDesaBinding extends Bindings {
);
// Daftarkan controller laporan
Get.lazyPut<LaporanController>(
() => LaporanController(),
Get.lazyPut<LaporanPenyaluranController>(
() => LaporanPenyaluranController(),
);
}
}

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;
}
}
}

View File

@ -6,7 +6,6 @@ import 'package:penyaluran_app/app/theme/app_theme.dart';
import 'package:penyaluran_app/app/utils/date_time_helper.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/views/qr_scanner_page.dart';
class DetailPenyaluranPage extends StatelessWidget {
@ -69,6 +68,9 @@ class DetailPenyaluranPage extends StatelessWidget {
controller.penyaluran.value?.alasanPembatalan != null &&
controller.penyaluran.value!.alasanPembatalan!.isNotEmpty)
_buildPembatalanSection(context),
if (controller.penyaluran.value?.status?.toUpperCase() ==
'TERLAKSANA')
_buildLaporanSection(context),
const SizedBox(height: 16),
_buildPenerimaPenyaluranSection(context),
const SizedBox(height: 24),
@ -1495,6 +1497,220 @@ class DetailPenyaluranPage extends StatelessWidget {
);
}
Widget _buildLaporanSection(BuildContext context) {
return Obx(() {
if (controller.isLoadingLaporan.value) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Container(
padding: const EdgeInsets.all(16),
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 8),
Text('Memuat data laporan...'),
],
),
),
),
);
}
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(
Icons.description_outlined,
color: AppTheme.successColor,
size: 24,
),
const SizedBox(width: 8),
const Text(
'Laporan Penyaluran',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.successColor,
),
),
],
),
if (controller.laporan.value != null)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppTheme.successColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppTheme.successColor.withOpacity(0.3),
),
),
child: Row(
children: [
Icon(
Icons.check_circle,
color: AppTheme.successColor,
size: 16,
),
const SizedBox(width: 4),
Text(
'Tersedia',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: AppTheme.successColor,
),
),
],
),
),
],
),
const Divider(height: 24),
if (controller.laporan.value == null)
Column(
children: [
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.assignment_late_outlined,
size: 50,
color: Colors.grey.shade400,
),
const SizedBox(height: 16),
Text(
'Belum ada laporan penyaluran',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 8),
Text(
'Buat laporan untuk mendokumentasikan hasil penyaluran bantuan',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade500,
),
),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: controller.navigateToLaporanCreate,
icon: const Icon(Icons.add_circle_outline),
label: const Text('Buat Laporan'),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
),
],
)
else
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow('Judul', controller.laporan.value!.judul),
_buildInfoRow(
'Tanggal Laporan',
controller.laporan.value?.tanggalLaporan != null
? DateTimeHelper.formatDateTime(
controller.laporan.value!.tanggalLaporan!)
: '-',
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () =>
controller.navigateToLaporanDetail(),
icon: const Icon(Icons.visibility),
label: const Text('Lihat Detail'),
style: OutlinedButton.styleFrom(
foregroundColor: AppTheme.primaryColor,
side: const BorderSide(
color: AppTheme.primaryColor),
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(width: 12),
if (controller.laporan.value?.beritaAcaraUrl != null &&
controller
.laporan.value!.beritaAcaraUrl!.isNotEmpty)
Expanded(
child: Obx(() => ElevatedButton.icon(
onPressed: controller.isExporting.value
? null
: () => controller.exportToPdf(),
icon: controller.isExporting.value
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(
Colors.white),
),
)
: const Icon(Icons.download),
label: Text(controller.isExporting.value
? 'Mengekspor...'
: 'Unduh PDF'),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.successColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 12),
disabledBackgroundColor:
AppTheme.successColor.withOpacity(0.7),
),
)),
),
],
),
],
),
],
),
),
);
});
}
List<PenerimaPenyaluranModel> _getFilteredPenerima() {
final query = searchQuery.value;
final status = statusFilter.value;

View File

@ -179,18 +179,25 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
const CircleAvatar(
CircleAvatar(
radius: 30,
backgroundColor: Colors.white,
child: Icon(
Icons.person,
size: 40,
color: AppTheme.primaryColor,
),
backgroundImage: controller.user?.avatar != null &&
controller.user!.avatar!.isNotEmpty
? NetworkImage(controller.user!.avatar!)
: null,
child: controller.user?.avatar == null ||
controller.user!.avatar!.isEmpty
? const Icon(
Icons.person,
size: 40,
color: AppTheme.primaryColor,
)
: null,
),
const SizedBox(height: 10),
Text(
controller.nama,
controller.user?.name ?? 'Petugas Desa',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
@ -198,203 +205,106 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
),
),
Text(
'Petugas Desa',
controller.user?.desa?.nama != null
? '${controller.user?.role} - ${controller.user!.desa!.nama}'
: controller.user?.role ?? 'PETUGAS_DESA',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
color: Colors.white.withAlpha(200),
fontSize: 14,
),
),
],
),
),
Obx(() => ListTile(
leading: const Icon(Icons.dashboard_outlined),
title: const Text('Dashboard'),
selected: controller.activeTabIndex.value == 0,
selectedColor: AppTheme.primaryColor,
onTap: () {
controller.changeTab(0);
Navigator.pop(context);
},
)),
Obx(() => ListTile(
leading: const Icon(Icons.calendar_today_outlined),
title: const Text('Penyaluran'),
selected: controller.activeTabIndex.value == 1,
selectedColor: AppTheme.primaryColor,
onTap: () {
controller.changeTab(1);
Navigator.pop(context);
},
)),
Obx(() => ListTile(
leading: Stack(
alignment: Alignment.center,
children: [
const Icon(Icons.handshake_outlined),
if (controller.jumlahMenunggu.value > 0)
Positioned(
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(10),
),
constraints: const BoxConstraints(
minWidth: 12,
minHeight: 12,
),
child: Text(
controller.jumlahMenunggu.value.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 8,
),
textAlign: TextAlign.center,
),
),
),
],
),
title: const Text('Penitipan'),
selected: controller.activeTabIndex.value == 2,
selectedColor: AppTheme.primaryColor,
onTap: () {
controller.changeTab(2);
Navigator.pop(context);
},
)),
Obx(() {
final int jumlahPengaduanDiproses = controller.jumlahDiproses.value;
return ListTile(
leading: Stack(
alignment: Alignment.center,
children: [
const Icon(Icons.report_problem_outlined),
// Selalu tampilkan badge untuk debugging
Positioned(
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
constraints: const BoxConstraints(
minWidth: 12,
minHeight: 12,
),
child: Text(
jumlahPengaduanDiproses.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 8,
),
textAlign: TextAlign.center,
),
),
),
],
),
title: const Text('Pengaduan'),
selected: controller.activeTabIndex.value == 3,
selectedColor: AppTheme.primaryColor,
onTap: () {
controller.changeTab(3);
Navigator.pop(context);
},
);
}),
Obx(() => ListTile(
leading: const Icon(Icons.inventory_2_outlined),
title: const Text('Stok Bantuan'),
selected: controller.activeTabIndex.value == 4,
selectedColor: AppTheme.primaryColor,
onTap: () {
controller.changeTab(4);
Navigator.pop(context);
},
)),
const Divider(),
ListTile(
leading: const Icon(Icons.people_outline),
title: const Text('Daftar Penerima'),
leading: const Icon(Icons.dashboard_outlined),
title: const Text('Dashboard'),
selected: controller.activeTabIndex.value == 0,
selectedColor: AppTheme.primaryColor,
onTap: () {
Navigator.pop(context); // Tutup drawer terlebih dahulu
Navigator.pop(context);
controller.changeTab(0);
},
),
ListTile(
leading: const Icon(Icons.handshake_outlined),
title: const Text('Penyaluran'),
selected: controller.activeTabIndex.value == 1,
selectedColor: AppTheme.primaryColor,
onTap: () {
Navigator.pop(context);
controller.changeTab(1);
},
),
ListTile(
leading: const Icon(Icons.inventory_2_outlined),
title: const Text('Penitipan'),
selected: controller.activeTabIndex.value == 2,
selectedColor: AppTheme.primaryColor,
onTap: () {
Navigator.pop(context);
controller.changeTab(2);
},
),
Obx(() => ListTile(
leading: controller.jumlahDiproses.value > 0
? Badge(
label: Text(controller.jumlahDiproses.value.toString()),
backgroundColor: Colors.red,
child: const Icon(Icons.support_outlined),
)
: const Icon(Icons.support_outlined),
title: const Text('Pengaduan'),
selected: controller.activeTabIndex.value == 3,
selectedColor: AppTheme.primaryColor,
onTap: () {
Navigator.pop(context);
controller.changeTab(3);
},
)),
ListTile(
leading: const Icon(Icons.inventory_outlined),
title: const Text('Stok Bantuan'),
selected: controller.activeTabIndex.value == 4,
selectedColor: AppTheme.primaryColor,
onTap: () {
Navigator.pop(context);
controller.changeTab(4);
},
),
ListTile(
leading: const Icon(Icons.person_add_outlined),
title: const Text('Kelola Penerima'),
onTap: () {
Navigator.pop(context);
Get.toNamed('/daftar-penerima');
},
),
ListTile(
leading: const Icon(Icons.volunteer_activism_outlined),
title: const Text('Daftar Donatur'),
leading: const Icon(Icons.people_outlined),
title: const Text('Kelola Donatur'),
onTap: () {
Navigator.pop(context); // Tutup drawer terlebih dahulu
Navigator.pop(context);
Get.toNamed('/daftar-donatur');
},
),
ListTile(
leading: Stack(
alignment: Alignment.center,
children: [
const Icon(Icons.notifications_outlined),
if (controller.jumlahNotifikasiBelumDibaca.value > 0)
Positioned(
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
constraints: const BoxConstraints(
minWidth: 12,
minHeight: 12,
),
child: Text(
controller.jumlahNotifikasiBelumDibaca.value.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 8,
),
textAlign: TextAlign.center,
),
),
),
],
),
title: const Text('Notifikasi'),
leading: const Icon(Icons.description_outlined),
title: const Text('Laporan Penyaluran'),
onTap: () {
Navigator.pop(context); // Tutup drawer terlebih dahulu
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NotifikasiView(),
),
);
Navigator.pop(context);
Get.toNamed('/laporan-penyaluran');
},
),
const Divider(),
ListTile(
leading: const Icon(Icons.person_outline),
title: const Text('Profil'),
onTap: () {
// Navigasi ke halaman profil
Navigator.pop(context);
Get.toNamed('/profile');
},
),
ListTile(
leading: const Icon(Icons.settings_outlined),
title: const Text('Pengaturan'),
onTap: () {
// Navigasi ke halaman pengaturan
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.logout),
title: const Text('Keluar'),

View File

@ -98,7 +98,7 @@ class RiwayatPenyaluranView extends GetView<JadwalPenyaluranController> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Daftar Penyaluran ${status == 'TERLAKSANA' ? 'terlaksana' : 'batal terlaksana'}',
'Daftar Penyaluran ${status == 'TERLAKSANA' ? 'Terlaksana' : 'Batal'}',
style:
Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,