Perbarui dependensi dengan menambahkan paket percent_indicator versi 4.2.4. Modifikasi file pubspec.yaml dan pubspec.lock untuk mencerminkan perubahan ini. Selain itu, perbarui status penerimaan di PelaksanaanPenyaluranController dari 'SUDAHMENERIMA' menjadi 'DITERIMA' untuk konsistensi. Tambahkan fungsionalitas baru di PetugasDesaDashboardController untuk memuat jadwal hari ini dan total penitipan terverifikasi. Perbarui tampilan di beberapa view untuk meningkatkan pengalaman pengguna dan konsistensi data.

This commit is contained in:
Khafidh Fuadi
2025-03-25 21:03:40 +07:00
parent 32736be867
commit 3b963178f4
20 changed files with 2191 additions and 818 deletions

View File

@ -4,16 +4,28 @@ import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_con
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_drawer.dart';
import 'package:penyaluran_app/app/widgets/app_bottom_navigation_bar.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart';
class WargaView extends GetView<WargaDashboardController> {
const WargaView({super.key});
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
WargaView({super.key});
@override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
// Tambahkan listener untuk refresh data saat fokus didapatkan kembali
// misalnya ketika kembali dari halaman profil
WidgetsBinding.instance.addPostFrameCallback((_) {
final focusNode = FocusNode();
FocusScope.of(context).requestFocus(focusNode);
focusNode.addListener(() {
if (focusNode.hasFocus) {
print('DEBUG WARGA: Halaman mendapatkan fokus, memuat ulang data');
controller.refreshData();
}
});
});
return Scaffold(
key: scaffoldKey,
@ -81,40 +93,7 @@ class WargaView extends GetView<WargaDashboardController> {
),
],
),
drawer: Obx(() => AppDrawer(
nama: controller.nama,
role: 'Warga',
desa: controller.desa,
notificationCount: controller.jumlahNotifikasiBelumDibaca.value,
onLogout: controller.logout,
menuItems: [
DrawerMenuItem(
icon: Icons.dashboard_outlined,
title: 'Dashboard',
isSelected: controller.activeTabIndex.value == 0,
onTap: () => controller.changeTab(0),
),
DrawerMenuItem(
icon: Icons.volunteer_activism_outlined,
title: 'Penerimaan',
isSelected: controller.activeTabIndex.value == 1,
onTap: () => controller.changeTab(1),
),
DrawerMenuItem(
icon: Icons.report_problem_outlined,
title: 'Pengaduan',
isSelected: controller.activeTabIndex.value == 2,
badgeCount: controller.totalPengaduanProses.value,
badgeColor: Colors.orange,
onTap: () => controller.changeTab(2),
),
DrawerMenuItem(
icon: Icons.description_outlined,
title: 'Laporan Penyaluran',
onTap: () => Get.toNamed('/laporan-penyaluran'),
),
],
)),
drawer: _buildDrawer(context),
body: Obx(() {
switch (controller.activeTabIndex.value) {
case 0:
@ -158,4 +137,276 @@ class WargaView extends GetView<WargaDashboardController> {
)),
);
}
Widget _buildDrawer(BuildContext context) {
// Muat ulang data foto profil ketika drawer dibuka
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.fotoProfil.isEmpty) {
controller.loadUserData();
}
});
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.profilePhotoUrl != null &&
controller.profilePhotoUrl!.isNotEmpty
? NetworkImage(controller.profilePhotoUrl!)
: null,
child: controller.profilePhotoUrl == null ||
controller.profilePhotoUrl!.isEmpty
? Icon(
Icons.person,
color: Colors.white,
size: 40,
)
: 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,
),
),
],
),
),
],
),
],
),
),
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,
badge: controller.totalPengaduanProses.value > 0
? controller.totalPengaduanProses.value.toString()
: null,
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.logout,
title: 'Keluar',
onTap: () {
Navigator.pop(context);
controller.logout();
},
isLogout: true,
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
'© ${DateTime.now().year} Aplikasi Penyaluran Bantuan',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
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,
),
),
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,
),
);
}
}