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:
@ -106,6 +106,16 @@ class WargaDashboardController extends GetxController {
|
||||
super.onInit();
|
||||
fetchData();
|
||||
loadUserData();
|
||||
|
||||
// Atau gunakan timer untuk refresh data secara periodik
|
||||
// Timer.periodic(Duration(seconds: 60), (_) => loadUserData());
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
// Perbarui data user dan foto profil saat halaman siap
|
||||
loadUserData();
|
||||
}
|
||||
|
||||
void loadUserData() {
|
||||
@ -133,7 +143,7 @@ class WargaDashboardController extends GetxController {
|
||||
wargaData.fotoProfil != null &&
|
||||
wargaData.fotoProfil!.isNotEmpty) {
|
||||
fotoProfil.value = wargaData.fotoProfil!;
|
||||
print('DEBUG WARGA: Foto profil: ${fotoProfil.value}');
|
||||
print('DEBUG WARGA: Foto profil dari roleData: ${fotoProfil.value}');
|
||||
}
|
||||
} else {
|
||||
print('DEBUG WARGA: User bukan warga');
|
||||
@ -142,10 +152,8 @@ class WargaDashboardController extends GetxController {
|
||||
print('DEBUG WARGA: userData null');
|
||||
}
|
||||
|
||||
// Cek dan ambil foto profil jika belum ada
|
||||
if (fotoProfil.isEmpty) {
|
||||
_fetchProfilePhoto();
|
||||
}
|
||||
// Ambil foto profil dari database
|
||||
_fetchProfilePhoto();
|
||||
}
|
||||
|
||||
// Metode untuk mengambil foto profil
|
||||
@ -156,12 +164,14 @@ class WargaDashboardController extends GetxController {
|
||||
final wargaData = await _supabaseService.client
|
||||
.from('warga')
|
||||
.select('foto_profil')
|
||||
.eq('user_id', user!.id)
|
||||
.single();
|
||||
.eq('id', user!.id) // Menggunakan id, bukan user_id
|
||||
.maybeSingle();
|
||||
|
||||
if (wargaData != null && wargaData['foto_profil'] != null) {
|
||||
fotoProfil.value = wargaData['foto_profil'];
|
||||
print('DEBUG WARGA: Foto profil dari API: ${fotoProfil.value}');
|
||||
} else {
|
||||
print('DEBUG WARGA: Foto profil tidak ditemukan atau null');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error fetching profile photo: $e');
|
||||
@ -586,4 +596,13 @@ class WargaDashboardController extends GetxController {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Metode untuk refresh data setelah update profil atau kembali ke halaman
|
||||
Future<void> refreshData() async {
|
||||
print('DEBUG WARGA: Memulai refresh data...');
|
||||
await _authController.refreshUserData(); // Refresh data dari server
|
||||
loadUserData(); // Muat ulang data ke variabel lokal
|
||||
fetchData(); // Ambil data terkait lainnya
|
||||
print('DEBUG WARGA: Refresh data selesai');
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
||||
children: [
|
||||
_buildWelcomeSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildStatisticSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildPenerimaanSummary(),
|
||||
const SizedBox(height: 24),
|
||||
_buildRecentPenerimaan(),
|
||||
@ -296,6 +298,144 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatisticSection() {
|
||||
// Data untuk statistik
|
||||
final totalBantuan = controller.penerimaPenyaluran.length;
|
||||
final totalDiterima = controller.penerimaPenyaluran
|
||||
.where((item) => item.statusPenerimaan == 'DITERIMA')
|
||||
.length;
|
||||
final totalBelumMenerima = controller.penerimaPenyaluran
|
||||
.where((item) => item.statusPenerimaan == 'BELUMMENERIMA')
|
||||
.length;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionHeader(
|
||||
title: 'Statistik Bantuan',
|
||||
titleStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatisticCard(
|
||||
icon: Icons.check_circle,
|
||||
color: Colors.green,
|
||||
title: 'Diterima',
|
||||
value: totalDiterima.toString(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildStatisticCard(
|
||||
icon: Icons.do_not_disturb,
|
||||
color: Colors.red,
|
||||
title: 'Belum Menerima',
|
||||
value: totalBelumMenerima.toString(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Progress bar untuk persentase bantuan yang diterima
|
||||
if (totalBantuan > 0) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Kemajuan Penerimaan Bantuan',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: LinearProgressIndicator(
|
||||
value: totalDiterima / totalBantuan,
|
||||
minHeight: 12,
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${(totalDiterima / totalBantuan * 100).toStringAsFixed(0)}% bantuan telah diterima',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatisticCard({
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required String title,
|
||||
required String value,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withOpacity(0.1),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPenerimaanSummary() {
|
||||
final currencyFormat = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
@ -324,49 +464,93 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
||||
}
|
||||
}
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.blue.shade50, Colors.white],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionHeader(
|
||||
title: 'Ringkasan Bantuan',
|
||||
titleStyle: const TextStyle(
|
||||
titleStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (totalUang > 0)
|
||||
_buildSummaryItem(
|
||||
icon: Icons.attach_money,
|
||||
color: Colors.green,
|
||||
title: 'Total Bantuan Uang',
|
||||
value: currencyFormat.format(totalUang),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (totalNonUang.isNotEmpty) ...[
|
||||
if (totalUang > 0) const SizedBox(height: 12),
|
||||
...totalNonUang.entries.map((entry) {
|
||||
return _buildSummaryItem(
|
||||
icon: Icons.inventory_2,
|
||||
color: Colors.blue,
|
||||
title: 'Total Bantuan ${entry.key}',
|
||||
value: '${entry.value} ${entry.key}',
|
||||
);
|
||||
}),
|
||||
],
|
||||
if (totalUang == 0 && totalNonUang.isEmpty)
|
||||
_buildSummaryItem(
|
||||
icon: Icons.info_outline,
|
||||
color: Colors.grey,
|
||||
title: 'Belum Ada Bantuan',
|
||||
value: 'Anda belum menerima bantuan',
|
||||
child: Column(
|
||||
children: [
|
||||
if (totalUang > 0)
|
||||
_buildSummaryItem(
|
||||
icon: Icons.attach_money,
|
||||
color: Colors.green,
|
||||
title: 'Total Bantuan Uang',
|
||||
value: currencyFormat.format(totalUang),
|
||||
),
|
||||
if (totalNonUang.isNotEmpty) ...[
|
||||
if (totalUang > 0)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Divider(height: 1),
|
||||
),
|
||||
...totalNonUang.entries.map((entry) {
|
||||
return Column(
|
||||
children: [
|
||||
_buildSummaryItem(
|
||||
icon: Icons.inventory_2,
|
||||
color: Colors.blue,
|
||||
title: 'Total Bantuan ${entry.key}',
|
||||
value: '${entry.value} ${entry.key}',
|
||||
),
|
||||
if (entry != totalNonUang.entries.last)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Divider(height: 1),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
if (totalUang == 0 && totalNonUang.isEmpty)
|
||||
_buildSummaryItem(
|
||||
icon: Icons.info_outline,
|
||||
color: Colors.grey,
|
||||
title: 'Belum Ada Bantuan',
|
||||
value: 'Anda belum menerima bantuan',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -422,53 +606,140 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
||||
|
||||
Widget _buildRecentPenerimaan() {
|
||||
if (controller.penerimaPenyaluran.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SectionHeader(
|
||||
title: 'Bantuan Terbaru',
|
||||
titleStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
size: 48,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Belum Ada Bantuan',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Data bantuan akan muncul di sini ketika Anda menerima bantuan.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final maxItems = controller.penerimaPenyaluran.length > 2
|
||||
? 2
|
||||
: controller.penerimaPenyaluran.length;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionHeader(
|
||||
title: 'Bantuan Terbaru',
|
||||
viewAllText: 'Lihat Semua',
|
||||
onViewAll: () {
|
||||
Get.toNamed(Routes.wargaPenerimaan);
|
||||
},
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: maxItems,
|
||||
itemBuilder: (context, index) {
|
||||
final item = controller.penerimaPenyaluran[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: BantuanCard(
|
||||
item: item,
|
||||
isCompact: true,
|
||||
onTap: () {
|
||||
Get.toNamed('/warga/detail-penerimaan',
|
||||
arguments: {'id': item.id});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (controller.penerimaPenyaluran.length > 2)
|
||||
Center(
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
Get.toNamed('/warga-penerimaan');
|
||||
},
|
||||
icon: const Icon(Icons.list),
|
||||
label: const Text('Lihat Semua Bantuan'),
|
||||
),
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionHeader(
|
||||
title: 'Bantuan Terbaru',
|
||||
viewAllText: 'Lihat Semua',
|
||||
onViewAll: () {
|
||||
Get.toNamed(Routes.wargaPenerimaan);
|
||||
},
|
||||
titleStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: maxItems,
|
||||
itemBuilder: (context, index) {
|
||||
final item = controller.penerimaPenyaluran[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: BantuanCard(
|
||||
item: item,
|
||||
isCompact: true,
|
||||
onTap: () {
|
||||
Get.toNamed('/warga/detail-penerimaan',
|
||||
arguments: {'id': item.id});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (controller.penerimaPenyaluran.length > 2)
|
||||
Center(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Get.toNamed(Routes.wargaPenerimaan);
|
||||
},
|
||||
icon: const Icon(Icons.list),
|
||||
label: const Text('Lihat Semua Bantuan'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: Colors.blue,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -328,8 +328,8 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
StatusBadge(
|
||||
status:
|
||||
penyaluran.statusPenerimaan ?? 'MENUNGGU',
|
||||
status: penyaluran.statusPenerimaan ??
|
||||
'BELUMMENERIMA',
|
||||
fontSize: 14,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
@ -423,7 +423,7 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
StatusBadge(
|
||||
status: penyaluran.statusPenerimaan ?? 'MENUNGGU',
|
||||
status: penyaluran.statusPenerimaan ?? 'BELUMMENERIMA',
|
||||
fontSize: 12,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user