Perbarui model Pengaduan dengan menambahkan getter isUang untuk memeriksa jenis bantuan. Modifikasi tampilan dan controller di modul donatur dan petugas desa untuk meningkatkan pengalaman pengguna, termasuk penggantian ikon dan penyesuaian format tampilan jumlah bantuan. Hapus kode yang tidak diperlukan untuk menjaga kebersihan kode.

This commit is contained in:
Khafidh Fuadi
2025-04-10 14:25:41 +07:00
parent 3f78514175
commit ca6c28f3d6
40 changed files with 3103 additions and 2270 deletions

View File

@ -7,8 +7,6 @@ import 'package:penyaluran_app/app/theme/app_theme.dart';
import 'package:penyaluran_app/app/utils/format_helper.dart';
import 'package:timeline_tile/timeline_tile.dart';
import 'package:image_picker/image_picker.dart';
import 'package:penyaluran_app/app/widgets/indicators/status_pill.dart';
import 'package:penyaluran_app/app/widgets/cards/info_card.dart';
import 'dart:io';
import 'package:penyaluran_app/app/widgets/widgets.dart';
@ -652,6 +650,76 @@ class WargaDetailPengaduanView extends GetView<WargaDashboardController> {
),
),
),
// Menampilkan foto pengaduan jika ada
if (pengaduan.fotoPengaduan != null &&
pengaduan.fotoPengaduan!.isNotEmpty) ...[
const SizedBox(height: 16),
Text(
'Foto Pengaduan:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.grey.shade800,
),
),
const SizedBox(height: 8),
Container(
height: 120,
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
child: Row(
children: pengaduan.fotoPengaduan!.map((foto) {
return GestureDetector(
onTap: () => _showFullScreenImage(context, foto),
child: Container(
width: 120,
height: 120,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
image: DecorationImage(
image: foto.startsWith('http')
? NetworkImage(foto)
: FileImage(File(foto)) as ImageProvider,
fit: BoxFit.cover,
),
),
child: Stack(
alignment: Alignment.bottomRight,
children: [
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: const Icon(
Icons.zoom_in,
color: Colors.white,
size: 16,
),
),
],
),
),
);
}).toList(),
),
),
),
],
const SizedBox(height: 16),
Row(
children: [
@ -926,7 +994,12 @@ class WargaDetailPengaduanView extends GetView<WargaDashboardController> {
'Nama Penyaluran', pengaduan.namaPenyaluran, Icons.assignment),
_buildInfoItem(
'Jenis Bantuan', pengaduan.jenisBantuan, Icons.category),
_buildInfoItem('Jumlah Bantuan', pengaduan.jumlahBantuan,
_buildInfoItem(
'Jumlah Bantuan',
pengaduan.isUang == true
? FormatHelper.formatRupiah(
double.tryParse(pengaduan.jumlahBantuan ?? '0'))
: pengaduan.jumlahBantuan,
Icons.shopping_basket),
_buildInfoItem(
'Deskripsi', pengaduan.deskripsiPenyaluran, Icons.description),

View File

@ -23,54 +23,6 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header DisalurKita dengan logo dan slogan
Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Image.asset(
'assets/images/logo-disalurkita.png',
width: 50,
height: 50,
),
const SizedBox(width: 15),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'DisalurKita',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF1565C0),
),
),
SizedBox(height: 5),
Text(
'Salurkan dengan Pasti, Pantau dengan Bukti',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
),
_buildWelcomeSection(),
const SizedBox(height: 24),
_buildStatisticSection(),
@ -540,7 +492,7 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
children: [
if (totalUang > 0)
_buildSummaryItem(
icon: Icons.attach_money,
icon: Icons.payment_rounded,
color: Colors.green,
title: 'Total Bantuan Uang',
value: FormatHelper.formatRupiah(totalUang),

View File

@ -158,18 +158,67 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// nama penyaluran
Text(
penyaluran.namaPenyaluran ?? 'Nama Penyaluran',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// deskripsi penyaluran
if (penyaluran.deskripsiPenyaluran != null &&
penyaluran.deskripsiPenyaluran!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
penyaluran.deskripsiPenyaluran!,
style: const TextStyle(
color: Colors.black,
fontSize: 16,
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, 1),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.description_outlined,
size: 18,
color: Colors.grey.shade600,
),
const SizedBox(width: 8),
Text(
'Deskripsi',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey.shade600,
),
),
],
),
const SizedBox(height: 8),
Text(
penyaluran.deskripsiPenyaluran!,
style: const TextStyle(
color: Colors.black87,
fontSize: 15,
height: 1.5,
),
),
],
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
@ -197,7 +246,7 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
),
child: Icon(
penyaluran.isUang == true
? Icons.attach_money
? Icons.payment_rounded
: Icons.inventory_2,
color: penyaluran.isUang == true
? Colors.green
@ -736,13 +785,17 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
// Pastikan menggunakan data terbaru dari model dan cetak ke log untuk debugging
final qrData = penyaluran.qrCodeHash ?? 'invalid-qr-code';
print('penyaluran.statusPenyaluran ${penyaluran.statusPenyaluran}');
print('penyaluran.statusPenerimaan ${penyaluran.statusPenerimaan}');
// Cek status penyaluran untuk disabled state
final bool isDisabled = penyaluran.statusPenyaluran != null &&
(penyaluran.statusPenyaluran!.toUpperCase() == 'DIJADWALKAN' ||
penyaluran.statusPenyaluran!.toUpperCase() == 'DISETUJUI' ||
penyaluran.statusPenyaluran!.toUpperCase() == 'BATALTERLAKSANA' ||
penyaluran.statusPenyaluran!.toUpperCase() == 'TERLAKSANA');
// Cek status penyaluran dan penerimaan untuk disabled state
final bool isDisabled = (penyaluran.statusPenyaluran != null &&
(penyaluran.statusPenyaluran!.toUpperCase() == 'DIJADWALKAN' ||
penyaluran.statusPenyaluran!.toUpperCase() == 'DISETUJUI' ||
penyaluran.statusPenyaluran!.toUpperCase() ==
'BATALTERLAKSANA' ||
penyaluran.statusPenyaluran!.toUpperCase() == 'TERLAKSANA')) ||
(penyaluran.statusPenerimaan != null &&
penyaluran.statusPenerimaan!.toUpperCase() == 'DITERIMA');
final String statusMessage;
if (isDisabled) {
@ -752,6 +805,9 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
} else if (penyaluran.statusPenyaluran!.toUpperCase() == 'TERLAKSANA') {
statusMessage =
'QR Code sudah digunakan pada penyaluran yang telah terlaksana';
} else if (penyaluran.statusPenerimaan != null &&
penyaluran.statusPenerimaan!.toUpperCase() == 'DITERIMA') {
statusMessage = 'QR Code sudah digunakan karena bantuan telah diterima';
} else {
statusMessage =
'QR Code belum dapat digunakan karena penyaluran belum terlaksana';

View File

@ -15,10 +15,6 @@ class WargaPenerimaanView extends GetView<WargaDashboardController> {
return const Center(child: CircularProgressIndicator());
}
// Debug print untuk melihat jumlah item
print(
'DEBUG: Jumlah penerimaan tersedia: ${controller.penerimaPenyaluran.length}');
return RefreshIndicator(
onRefresh: () async {
// Tambahkan delay untuk memastikan refresh indicator terlihat
@ -102,9 +98,7 @@ class WargaPenerimaanView extends GetView<WargaDashboardController> {
}
Widget _buildPenerimaanList(BuildContext context) {
// Debug print untuk melihat jumlah item
print(
'DEBUG: Membangun ListView dengan ${controller.penerimaPenyaluran.length} item bantuan');
// Menggunakan CustomScrollView dan SliverList untuk layout yang lebih stabil
// Menggunakan CustomScrollView dan SliverList untuk layout yang lebih stabil
return CustomScrollView(
@ -122,9 +116,6 @@ class WargaPenerimaanView extends GetView<WargaDashboardController> {
final item = controller.penerimaPenyaluran[index];
// Debug
print('DEBUG: Membangun item $index dengan id: ${item.id}');
// Menggunakan SizedBox untuk memberikan batas lebar dan tinggi
return SizedBox(
width: MediaQuery.of(context).size.width,

View File

@ -289,15 +289,22 @@ class WargaPengaduanView extends GetView<WargaDashboardController> {
Expanded(
child: _buildInfoItem(
'Jenis',
item.jenisBantuan ??
item.stokBantuan?[
'nama'] ??
item.jenisBantuan ??
"Tidak tersedia",
),
),
Expanded(
child: _buildInfoItem(
'Jumlah',
item.jumlahBantuan ??
"Tidak tersedia",
item.isUang
? FormatHelper.formatRupiah(
double.tryParse(item
.jumlahBantuan
.toString()) ??
0)
: '${item.jumlahBantuan} ${item.stokBantuan?['satuan'] ?? ''}',
),
),
],

View File

@ -5,6 +5,7 @@ import 'package:penyaluran_app/app/modules/warga/views/warga_dashboard_view.dart
import 'package:penyaluran_app/app/modules/warga/views/warga_penerimaan_view.dart';
import 'package:penyaluran_app/app/modules/warga/views/warga_pengaduan_view.dart';
import 'package:penyaluran_app/app/widgets/app_bottom_navigation_bar.dart';
import 'package:penyaluran_app/app/widgets/app_drawer.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart';
class WargaView extends GetView<WargaDashboardController> {
@ -133,276 +134,83 @@ class WargaView extends GetView<WargaDashboardController> {
}
});
return Drawer(
child: Column(
children: [
Container(
decoration: BoxDecoration(
gradient: AppTheme.primaryGradient,
),
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 16,
bottom: 24,
left: 16,
right: 16),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: Offset(0, 5),
),
],
),
child: CircleAvatar(
radius: 40,
backgroundColor: Colors.white70,
backgroundImage: controller.fotoProfil.value.isNotEmpty
? NetworkImage(controller.fotoProfil.value)
: null,
child: controller.fotoProfil.isEmpty
? Text(
controller.nama.isNotEmpty
? controller.nama.substring(0, 1).toUpperCase()
: '?',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 24,
),
)
: null,
),
),
SizedBox(height: 16),
Text(
'Halo,',
style: TextStyle(
color: Colors.white70,
fontSize: 16,
),
),
Text(
controller.nama,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 22,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'Warga',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
SizedBox(width: 8),
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.location_on,
color: Colors.white,
size: 14,
),
SizedBox(width: 4),
Text(
controller.desa ?? 'Tidak ada desa',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
],
),
),
],
),
],
),
return Obx(() {
Map<String, List<DrawerMenuItem>> menuCategories = {
'Menu Utama': [
DrawerMenuItem(
icon: Icons.dashboard_outlined,
activeIcon: Icons.dashboard,
title: 'Dashboard',
isSelected: controller.activeTabIndex.value == 0,
onTap: () {
controller.activeTabIndex.value = 0;
},
),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: [
_buildMenuCategory('Menu Utama'),
Obx(() => _buildMenuItem(
icon: Icons.dashboard_outlined,
activeIcon: Icons.dashboard,
title: 'Dashboard',
isSelected: controller.activeTabIndex.value == 0,
onTap: () {
Navigator.pop(context);
controller.changeTab(0);
},
)),
Obx(() => _buildMenuItem(
icon: Icons.volunteer_activism_outlined,
activeIcon: Icons.volunteer_activism,
title: 'Penerimaan',
isSelected: controller.activeTabIndex.value == 1,
onTap: () {
Navigator.pop(context);
controller.changeTab(1);
},
)),
Obx(() => _buildMenuItem(
icon: Icons.report_problem_outlined,
activeIcon: Icons.report_problem,
title: 'Pengaduan',
isSelected: controller.activeTabIndex.value == 2,
onTap: () {
Navigator.pop(context);
controller.changeTab(2);
},
)),
_buildMenuCategory('Pengaturan'),
_buildMenuItem(
icon: Icons.person_outline,
activeIcon: Icons.person,
title: 'Profil',
onTap: () async {
Navigator.pop(context);
await Get.toNamed('/profile');
// Refresh data ketika kembali dari profil
controller.refreshData();
},
),
_buildMenuItem(
icon: Icons.info_outline,
activeIcon: Icons.info,
title: 'Tentang Kami',
onTap: () {
Navigator.pop(context);
Get.toNamed('/about');
},
),
_buildMenuItem(
icon: Icons.logout,
title: 'Keluar',
onTap: () {
Navigator.pop(context);
controller.logout();
},
isLogout: true,
),
],
),
DrawerMenuItem(
icon: Icons.volunteer_activism_outlined,
activeIcon: Icons.volunteer_activism,
title: 'Penerimaan Bantuan',
isSelected: controller.activeTabIndex.value == 1,
badgeCount: controller.totalPenyaluranDiterima.value > 0
? controller.totalPenyaluranDiterima.value
: null,
badgeColor: Colors.green,
onTap: () {
controller.activeTabIndex.value = 1;
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
'© ${DateTime.now().year} DisalurKita',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
DrawerMenuItem(
icon: Icons.report_problem_outlined,
activeIcon: Icons.report_problem,
title: 'Pengaduan',
isSelected: controller.activeTabIndex.value == 2,
badgeCount: controller.totalPengaduanProses.value > 0
? controller.totalPengaduanProses.value
: null,
badgeColor: Colors.orange,
onTap: () {
controller.activeTabIndex.value = 2;
},
),
],
),
);
}
Widget _buildMenuCategory(String title) {
return Padding(
padding: const EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8),
child: Text(
title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
),
);
}
Widget _buildMenuItem({
required IconData icon,
IconData? activeIcon,
required String title,
bool isSelected = false,
String? badge,
required Function() onTap,
bool isLogout = false,
}) {
return AnimatedContainer(
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
color: isSelected
? AppTheme.primaryColor.withOpacity(0.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child: ListTile(
leading: Icon(
isSelected ? (activeIcon ?? icon) : icon,
color: isSelected
? AppTheme.primaryColor
: (isLogout ? Colors.red : null),
),
title: Text(
title,
style: TextStyle(
color: isSelected
? AppTheme.primaryColor
: (isLogout ? Colors.red : null),
fontWeight: isSelected ? FontWeight.bold : null,
'Pengaturan': [
DrawerMenuItem(
icon: Icons.person_outline,
activeIcon: Icons.person,
title: 'Profil',
onTap: () {
Get.toNamed('/profile');
},
),
),
trailing: badge != null
? Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(10),
),
constraints: BoxConstraints(
minWidth: 20,
minHeight: 20,
),
child: Text(
badge,
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
textAlign: TextAlign.center,
),
)
: null,
onTap: onTap,
),
);
DrawerMenuItem(
icon: Icons.info_outline,
activeIcon: Icons.info,
title: 'Tentang Kami',
onTap: () {
Get.toNamed('/about');
},
),
DrawerMenuItem(
icon: Icons.logout,
title: 'Keluar',
isLogout: true,
onTap: () {
controller.logout();
},
),
],
};
return AppDrawer(
nama: controller.nama,
role: 'Warga',
desa: controller.desa,
avatar: controller.fotoProfil.value,
menuItems: const [], // Tidak digunakan karena menggunakan menuCategories
menuCategories: menuCategories,
onLogout: controller.logout,
footerText: '© ${DateTime.now().year} DisalurKita',
);
});
}
}