Hapus tampilan dashboard Petugas Desa dan perbarui routing
- Hapus file petugas_desa_dashboard_view.dart - Perbarui app_pages.dart untuk menggunakan PetugasDesaView dan PetugasDesaBinding - Pindahkan logika dashboard ke modul petugas_desa yang baru - Sesuaikan routing untuk dashboard Petugas Desa
This commit is contained in:
@ -1,632 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:penyaluran_app/app/modules/dashboard/controllers/dashboard_controller.dart';
|
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
|
||||||
import 'package:penyaluran_app/app/widgets/navigation_button.dart';
|
|
||||||
import 'package:penyaluran_app/app/widgets/statistic_card.dart';
|
|
||||||
import 'package:penyaluran_app/app/widgets/status_pill.dart';
|
|
||||||
|
|
||||||
class PetugasDesaDashboardView extends GetView<DashboardController> {
|
|
||||||
const PetugasDesaDashboardView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
key: scaffoldKey,
|
|
||||||
drawer: Drawer(
|
|
||||||
child: ListView(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
children: [
|
|
||||||
DrawerHeader(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: AppTheme.primaryGradient,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const CircleAvatar(
|
|
||||||
radius: 30,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
child: Icon(
|
|
||||||
Icons.person,
|
|
||||||
size: 40,
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
'Petugas Desa',
|
|
||||||
style: textTheme.titleLarge?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
controller.user?.email ?? 'email@example.com',
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.white70,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: SvgPicture.asset(
|
|
||||||
'assets/icons/home-icon.svg',
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
title: const Text('Beranda'),
|
|
||||||
onTap: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.calendar_today),
|
|
||||||
title: const Text('Jadwal'),
|
|
||||||
onTap: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.notifications),
|
|
||||||
title: const Text('Notifikasi'),
|
|
||||||
onTap: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.inventory),
|
|
||||||
title: const Text('Inventaris'),
|
|
||||||
onTap: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.logout),
|
|
||||||
title: const Text('Logout'),
|
|
||||||
onTap: controller.logout,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Obx(() {
|
|
||||||
if (controller.isLoading.value) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => scaffoldKey.currentState?.openDrawer(),
|
|
||||||
icon: const Icon(Icons.menu),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
|
|
||||||
// Header dengan greeting
|
|
||||||
_buildGreetingHeader(textTheme),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Jadwal penyaluran hari ini
|
|
||||||
_buildScheduleCard(
|
|
||||||
textTheme,
|
|
||||||
title: 'Jadwal Penyaluran Hari ini',
|
|
||||||
location: 'Kantor Kepala Desa (Beras)',
|
|
||||||
dateTime: '15 April 2023, 13:00 - 14:00',
|
|
||||||
isToday: true,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Jadwal penyaluran mendatang
|
|
||||||
_buildScheduleCard(
|
|
||||||
textTheme,
|
|
||||||
title: 'Jadwal Penyaluran Mendatang',
|
|
||||||
location: 'Balai Desa A (Sembako)',
|
|
||||||
dateTime: '17 April 2023, 13:00 - 14:00',
|
|
||||||
isToday: false,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Statistik penyaluran
|
|
||||||
_buildStatisticsRow(textTheme),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Progress penyaluran
|
|
||||||
_buildProgressSection(textTheme),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Daftar penerima
|
|
||||||
_buildRecipientsList(textTheme),
|
|
||||||
|
|
||||||
// Daftar Donasi
|
|
||||||
_buildDonationList(textTheme),
|
|
||||||
// Daftar Donatur
|
|
||||||
_buildDonorsList(textTheme),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
|
||||||
currentIndex: 0,
|
|
||||||
type: BottomNavigationBarType.fixed,
|
|
||||||
selectedLabelStyle: textTheme.bodySmall,
|
|
||||||
unselectedLabelStyle: textTheme.bodySmall,
|
|
||||||
items: [
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
'assets/icons/home-icon.svg',
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
label: 'Beranda',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
'assets/icons/jadwal-icon.svg',
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
label: 'Jadwal',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
'assets/icons/notif-icon.svg',
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
label: 'Notifikasi',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
'assets/icons/inventory-icon.svg',
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
label: 'Inventaris',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildGreetingHeader(TextTheme textTheme) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withAlpha(26), // 0.1 * 255 ≈ 26
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 3,
|
|
||||||
offset: const Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Selamat Datang, Ahmad!',
|
|
||||||
style: textTheme.headlineSmall?.copyWith(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
'Kamu Login Sebagai Petugas Desa Jatihurip.',
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildScheduleCard(
|
|
||||||
TextTheme textTheme, {
|
|
||||||
required String title,
|
|
||||||
required String location,
|
|
||||||
required String dateTime,
|
|
||||||
bool isToday = true,
|
|
||||||
}) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: AppTheme.primaryGradient,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.white.withAlpha(204), // 0.8 * 255 ≈ 204
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
location,
|
|
||||||
style: textTheme.titleLarge?.copyWith(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
dateTime,
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatisticsRow(TextTheme textTheme) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: StatisticCard(
|
|
||||||
title: 'Penitipan',
|
|
||||||
count: '3',
|
|
||||||
subtitle: 'Perlu Konfirmasi',
|
|
||||||
height: 120,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: StatisticCard(
|
|
||||||
title: 'Penjadwalan',
|
|
||||||
count: '1',
|
|
||||||
subtitle: 'Perlu Konfirmasi',
|
|
||||||
height: 120,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: StatisticCard(
|
|
||||||
title: 'Pengaduan',
|
|
||||||
count: '1',
|
|
||||||
subtitle: 'Perlu Tindakan',
|
|
||||||
height: 120,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildProgressSection(TextTheme textTheme) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: AppTheme.primaryGradient,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Progress Penyaluran',
|
|
||||||
style: textTheme.titleMedium?.copyWith(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: 0.7,
|
|
||||||
minHeight: 10,
|
|
||||||
backgroundColor: Colors.white.withOpacity(0.3),
|
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
'70% Selesai',
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildProgressDetailRow('Telah Menerima', 70, textTheme),
|
|
||||||
_buildProgressDetailRow('Dijedwalkan', 20, textTheme),
|
|
||||||
_buildProgressDetailRow('Belum Dijadwalkan', 10, textTheme),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
'Total : 100 Penerima, Telah Disalurkan : 70 Penerima, Belum Disalurkan : 30',
|
|
||||||
style: textTheme.bodySmall?.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.white.withOpacity(0.8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildProgressDetailRow(String label, int value, TextTheme textTheme) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 5,
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 5,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 8,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 8,
|
|
||||||
width: (MediaQuery.of(Get.context!).size.width - 32) *
|
|
||||||
0.7 *
|
|
||||||
(value / 100),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(
|
|
||||||
'$value%',
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRecipientsList(TextTheme textTheme) {
|
|
||||||
return _buildList(
|
|
||||||
textTheme: textTheme,
|
|
||||||
title: 'Daftar Penerima',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
'title': 'Siti Rahayu',
|
|
||||||
'subtitle': '3201020107030010',
|
|
||||||
'status': 'Selesai'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Siti Rahayu',
|
|
||||||
'subtitle': '3201020107030010',
|
|
||||||
'status': 'Selesai'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Siti Rahayu',
|
|
||||||
'subtitle': '3201020107030010',
|
|
||||||
'status': 'Selesai'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
idLabel: 'NIK',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDonorsList(TextTheme textTheme) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Daftar Donatur',
|
|
||||||
style: textTheme.titleMedium?.copyWith(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: Text(
|
|
||||||
'Lihat Semua',
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildDonorItem('PT Sejahtera', 'D-2023-001', textTheme),
|
|
||||||
_buildDonorItem('Yayasan Peduli', 'D-2023-002', textTheme),
|
|
||||||
_buildDonorItem('CV Makmur', 'D-2023-003', textTheme),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDonorItem(String name, String id, TextTheme textTheme) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: AppTheme.primaryGradient,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: ListTile(
|
|
||||||
title: Text(
|
|
||||||
name,
|
|
||||||
style: textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
id,
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: NavigationButton(
|
|
||||||
label: 'Detail',
|
|
||||||
icon: Icons.arrow_forward_ios,
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//daftar donasi
|
|
||||||
Widget _buildDonationList(TextTheme textTheme) {
|
|
||||||
return _buildList(
|
|
||||||
textTheme: textTheme,
|
|
||||||
title: 'Daftar Donasi',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
'title': 'Rp 100.000',
|
|
||||||
'subtitle': 'Siti Rahayu',
|
|
||||||
'status': 'Selesai',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Rp 100.000',
|
|
||||||
'subtitle': 'Siti Rahayu',
|
|
||||||
'status': 'Selesai',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
idLabel: '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildList({
|
|
||||||
required TextTheme textTheme,
|
|
||||||
required String title,
|
|
||||||
required List<Map<String, String>> items,
|
|
||||||
required String idLabel,
|
|
||||||
}) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: textTheme.titleMedium?.copyWith(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Lihat Semua',
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.chevron_right,
|
|
||||||
size: 16,
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
...items.map((item) => _buildItem(
|
|
||||||
item['title'] ?? '',
|
|
||||||
item['subtitle'] ?? '',
|
|
||||||
item['status'] ?? '',
|
|
||||||
textTheme,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildItem(
|
|
||||||
String title, String subtitle, String status, TextTheme textTheme) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: AppTheme.primaryGradient,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: ListTile(
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
StatusPill(status: status, backgroundColor: AppTheme.verifiedColor),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
subtitle,
|
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: NavigationButton(
|
|
||||||
label: 'Detail',
|
|
||||||
icon: Icons.arrow_forward_ios,
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||||
|
|
||||||
|
class PetugasDesaBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut<PetugasDesaController>(
|
||||||
|
() => PetugasDesaController(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
56
lib/app/modules/petugas_desa/components/greeting_header.dart
Normal file
56
lib/app/modules/petugas_desa/components/greeting_header.dart
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class GreetingHeader extends StatelessWidget {
|
||||||
|
final String name;
|
||||||
|
final String role;
|
||||||
|
final String? desa;
|
||||||
|
|
||||||
|
const GreetingHeader({
|
||||||
|
super.key,
|
||||||
|
required this.name,
|
||||||
|
required this.role,
|
||||||
|
this.desa,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withAlpha(26), // 0.1 * 255 ≈ 26
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Selamat Datang, $name!',
|
||||||
|
style: textTheme.headlineSmall?.copyWith(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
'Kamu Login Sebagai $role${desa != null ? ' $desa' : ''}.',
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
133
lib/app/modules/petugas_desa/components/progress_section.dart
Normal file
133
lib/app/modules/petugas_desa/components/progress_section.dart
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class ProgressSection extends StatelessWidget {
|
||||||
|
final double progressValue;
|
||||||
|
final int total;
|
||||||
|
final int distributed;
|
||||||
|
final int scheduled;
|
||||||
|
final int unscheduled;
|
||||||
|
|
||||||
|
const ProgressSection({
|
||||||
|
super.key,
|
||||||
|
required this.progressValue,
|
||||||
|
required this.total,
|
||||||
|
required this.distributed,
|
||||||
|
required this.scheduled,
|
||||||
|
required this.unscheduled,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppTheme.primaryGradient,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Progress Penyaluran',
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: progressValue,
|
||||||
|
minHeight: 10,
|
||||||
|
backgroundColor: Colors.white.withOpacity(0.3),
|
||||||
|
valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'${(progressValue * 100).toInt()}% Selesai',
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildProgressDetailRow('Telah Menerima', distributed, textTheme),
|
||||||
|
_buildProgressDetailRow('Dijedwalkan', scheduled, textTheme),
|
||||||
|
_buildProgressDetailRow('Belum Dijadwalkan', unscheduled, textTheme),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
'Total : $total Penerima, Telah Disalurkan : $distributed Penerima, Belum Disalurkan : ${total - distributed}',
|
||||||
|
style: textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white.withOpacity(0.8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProgressDetailRow(String label, int value, TextTheme textTheme) {
|
||||||
|
final percentage = total > 0 ? (value / total) : 0.0;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.3),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 8,
|
||||||
|
width: (MediaQuery.of(Get.context!).size.width - 32) *
|
||||||
|
0.5 *
|
||||||
|
percentage,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
'$value',
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
65
lib/app/modules/petugas_desa/components/schedule_card.dart
Normal file
65
lib/app/modules/petugas_desa/components/schedule_card.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class ScheduleCard extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String location;
|
||||||
|
final String dateTime;
|
||||||
|
final bool isToday;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
const ScheduleCard({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.location,
|
||||||
|
required this.dateTime,
|
||||||
|
this.isToday = true,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppTheme.primaryGradient,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white.withAlpha(204), // 0.8 * 255 ≈ 204
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
location,
|
||||||
|
style: textTheme.titleLarge?.copyWith(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
dateTime,
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,318 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.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 PetugasDesaController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
final Rx<Map<String, dynamic>?> roleData = Rx<Map<String, dynamic>?>(null);
|
||||||
|
|
||||||
|
// Indeks kategori yang dipilih untuk filter
|
||||||
|
final RxInt selectedCategoryIndex = 0.obs;
|
||||||
|
|
||||||
|
// Indeks tab yang aktif di bottom navigation bar
|
||||||
|
final RxInt activeTabIndex = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk dashboard
|
||||||
|
final RxInt totalPenerima = 0.obs;
|
||||||
|
final RxInt totalBantuan = 0.obs;
|
||||||
|
final RxInt totalPenyaluran = 0.obs;
|
||||||
|
final RxDouble progressPenyaluran = 0.0.obs;
|
||||||
|
|
||||||
|
// Data untuk jadwal
|
||||||
|
final RxList<Map<String, dynamic>> jadwalHariIni =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
final RxList<Map<String, dynamic>> jadwalMendatang =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
final RxList<Map<String, dynamic>> jadwalSelesai =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
|
||||||
|
// Data untuk notifikasi
|
||||||
|
final RxList<Map<String, dynamic>> notifikasiBelumDibaca =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk inventaris
|
||||||
|
final RxList<Map<String, dynamic>> daftarInventaris =
|
||||||
|
<Map<String, dynamic>>[].obs;
|
||||||
|
final RxDouble totalStok = 0.0.obs;
|
||||||
|
final RxDouble stokMasuk = 0.0.obs;
|
||||||
|
final RxDouble stokKeluar = 0.0.obs;
|
||||||
|
|
||||||
|
// Controller untuk pencarian
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
String get role => user?.role ?? 'PETUGASDESA';
|
||||||
|
String get nama => user?.name ?? 'Petugas Desa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadRoleData();
|
||||||
|
loadDashboardData();
|
||||||
|
loadJadwalData();
|
||||||
|
loadNotifikasiData();
|
||||||
|
loadInventarisData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadRoleData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
if (user != null) {
|
||||||
|
final data = await _supabaseService.getRoleSpecificData(role);
|
||||||
|
roleData.value = data;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading role data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadDashboardData() async {
|
||||||
|
try {
|
||||||
|
// Simulasi data untuk dashboard
|
||||||
|
await Future.delayed(const Duration(milliseconds: 800));
|
||||||
|
|
||||||
|
totalPenerima.value = 120;
|
||||||
|
totalBantuan.value = 5;
|
||||||
|
totalPenyaluran.value = 8;
|
||||||
|
progressPenyaluran.value = 0.75;
|
||||||
|
|
||||||
|
// Di implementasi nyata, data akan diambil dari Supabase
|
||||||
|
// final result = await _supabaseService.getDashboardData();
|
||||||
|
// totalPenerima.value = result['total_penerima'] ?? 0;
|
||||||
|
// totalBantuan.value = result['total_bantuan'] ?? 0;
|
||||||
|
// totalPenyaluran.value = result['total_penyaluran'] ?? 0;
|
||||||
|
// progressPenyaluran.value = result['progress_penyaluran'] ?? 0.0;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading dashboard data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadJadwalData() async {
|
||||||
|
try {
|
||||||
|
// Simulasi data untuk jadwal
|
||||||
|
await Future.delayed(const Duration(milliseconds: 600));
|
||||||
|
|
||||||
|
jadwalHariIni.value = [
|
||||||
|
{
|
||||||
|
'id': '1',
|
||||||
|
'lokasi': 'Balai Desa Sukamaju',
|
||||||
|
'jenis_bantuan': 'Beras',
|
||||||
|
'tanggal': '15 April 2023',
|
||||||
|
'waktu': '09:00 - 12:00',
|
||||||
|
'status': 'aktif',
|
||||||
|
'jumlah_penerima': 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '2',
|
||||||
|
'lokasi': 'Pos RW 03',
|
||||||
|
'jenis_bantuan': 'Paket Sembako',
|
||||||
|
'tanggal': '15 April 2023',
|
||||||
|
'waktu': '13:00 - 15:00',
|
||||||
|
'status': 'aktif',
|
||||||
|
'jumlah_penerima': 30,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
jadwalMendatang.value = [
|
||||||
|
{
|
||||||
|
'id': '3',
|
||||||
|
'lokasi': 'Balai Desa Sukamaju',
|
||||||
|
'jenis_bantuan': 'Beras',
|
||||||
|
'tanggal': '22 April 2023',
|
||||||
|
'waktu': '09:00 - 12:00',
|
||||||
|
'status': 'terjadwal',
|
||||||
|
'jumlah_penerima': 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '4',
|
||||||
|
'lokasi': 'Pos RW 05',
|
||||||
|
'jenis_bantuan': 'Paket Sembako',
|
||||||
|
'tanggal': '23 April 2023',
|
||||||
|
'waktu': '13:00 - 15:00',
|
||||||
|
'status': 'terjadwal',
|
||||||
|
'jumlah_penerima': 35,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
jadwalSelesai.value = [
|
||||||
|
{
|
||||||
|
'id': '5',
|
||||||
|
'lokasi': 'Balai Desa Sukamaju',
|
||||||
|
'jenis_bantuan': 'Beras',
|
||||||
|
'tanggal': '8 April 2023',
|
||||||
|
'waktu': '09:00 - 12:00',
|
||||||
|
'status': 'selesai',
|
||||||
|
'jumlah_penerima': 48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '6',
|
||||||
|
'lokasi': 'Pos RW 02',
|
||||||
|
'jenis_bantuan': 'Paket Sembako',
|
||||||
|
'tanggal': '9 April 2023',
|
||||||
|
'waktu': '13:00 - 15:00',
|
||||||
|
'status': 'selesai',
|
||||||
|
'jumlah_penerima': 32,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Di implementasi nyata, data akan diambil dari Supabase
|
||||||
|
// final result = await _supabaseService.getJadwalData();
|
||||||
|
// jadwalHariIni.value = result['hari_ini'] ?? [];
|
||||||
|
// jadwalMendatang.value = result['mendatang'] ?? [];
|
||||||
|
// jadwalSelesai.value = result['selesai'] ?? [];
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading jadwal data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadNotifikasiData() async {
|
||||||
|
try {
|
||||||
|
// Simulasi data untuk notifikasi
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
// Hitung jumlah notifikasi yang belum dibaca
|
||||||
|
final List<Map<String, dynamic>> notifikasi = [
|
||||||
|
{
|
||||||
|
'id': '1',
|
||||||
|
'judul': 'Jadwal Penyaluran Baru',
|
||||||
|
'pesan': 'Jadwal penyaluran beras telah ditambahkan untuk hari ini',
|
||||||
|
'waktu': '08:30',
|
||||||
|
'dibaca': false,
|
||||||
|
'tanggal': 'hari_ini',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '2',
|
||||||
|
'judul': 'Pengajuan Bantuan Baru',
|
||||||
|
'pesan': 'Ada 3 pengajuan bantuan baru yang perlu diverifikasi',
|
||||||
|
'waktu': '10:15',
|
||||||
|
'dibaca': false,
|
||||||
|
'tanggal': 'hari_ini',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '3',
|
||||||
|
'judul': 'Laporan Penyaluran',
|
||||||
|
'pesan':
|
||||||
|
'Laporan penyaluran bantuan tanggal 14 April 2023 telah selesai',
|
||||||
|
'waktu': '16:45',
|
||||||
|
'dibaca': true,
|
||||||
|
'tanggal': 'kemarin',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
notifikasiBelumDibaca.value =
|
||||||
|
notifikasi.where((n) => n['dibaca'] == false).toList();
|
||||||
|
jumlahNotifikasiBelumDibaca.value = notifikasiBelumDibaca.length;
|
||||||
|
|
||||||
|
// Di implementasi nyata, data akan diambil dari Supabase
|
||||||
|
// final result = await _supabaseService.getNotifikasiData();
|
||||||
|
// notifikasiBelumDibaca.value = result.where((n) => n['dibaca'] == false).toList();
|
||||||
|
// jumlahNotifikasiBelumDibaca.value = notifikasiBelumDibaca.length;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading notifikasi data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadInventarisData() async {
|
||||||
|
try {
|
||||||
|
// Simulasi data untuk inventaris
|
||||||
|
await Future.delayed(const Duration(milliseconds: 700));
|
||||||
|
|
||||||
|
daftarInventaris.value = [
|
||||||
|
{
|
||||||
|
'id': '1',
|
||||||
|
'nama': 'Beras',
|
||||||
|
'jenis': 'Sembako',
|
||||||
|
'stok': '750 kg',
|
||||||
|
'stok_angka': 750.0,
|
||||||
|
'lokasi': 'Gudang Utama',
|
||||||
|
'tanggal_masuk': '10 April 2023',
|
||||||
|
'kadaluarsa': '10 April 2024',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '2',
|
||||||
|
'nama': 'Minyak Goreng',
|
||||||
|
'jenis': 'Sembako',
|
||||||
|
'stok': '250 liter',
|
||||||
|
'stok_angka': 250.0,
|
||||||
|
'lokasi': 'Gudang Utama',
|
||||||
|
'tanggal_masuk': '12 April 2023',
|
||||||
|
'kadaluarsa': '12 Oktober 2023',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': '3',
|
||||||
|
'nama': 'Paket Sembako',
|
||||||
|
'jenis': 'Paket Bantuan',
|
||||||
|
'stok': '100 paket',
|
||||||
|
'stok_angka': 100.0,
|
||||||
|
'lokasi': 'Gudang Cabang',
|
||||||
|
'tanggal_masuk': '15 April 2023',
|
||||||
|
'kadaluarsa': '15 Juli 2023',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Hitung total stok, stok masuk, dan stok keluar
|
||||||
|
totalStok.value = daftarInventaris.fold(
|
||||||
|
0, (sum, item) => sum + (item['stok_angka'] as double));
|
||||||
|
stokMasuk.value = 500.0; // Contoh data
|
||||||
|
stokKeluar.value = 350.0; // Contoh data
|
||||||
|
|
||||||
|
// Di implementasi nyata, data akan diambil dari Supabase
|
||||||
|
// final result = await _supabaseService.getInventarisData();
|
||||||
|
// daftarInventaris.value = result['daftar'] ?? [];
|
||||||
|
// totalStok.value = result['total_stok'] ?? 0.0;
|
||||||
|
// stokMasuk.value = result['stok_masuk'] ?? 0.0;
|
||||||
|
// stokKeluar.value = result['stok_keluar'] ?? 0.0;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading inventaris data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tandaiNotifikasiDibaca(String id) {
|
||||||
|
// Implementasi untuk menandai notifikasi sebagai dibaca
|
||||||
|
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status notifikasi
|
||||||
|
// await _supabaseService.markNotificationAsRead(id);
|
||||||
|
|
||||||
|
// Perbarui data lokal
|
||||||
|
loadNotifikasiData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tambahInventaris(Map<String, dynamic> data) {
|
||||||
|
// Implementasi untuk menambah inventaris
|
||||||
|
// Di implementasi nyata, akan memanggil Supabase untuk menambah data inventaris
|
||||||
|
// await _supabaseService.addInventory(data);
|
||||||
|
|
||||||
|
// Perbarui data lokal
|
||||||
|
loadInventarisData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hapusInventaris(String id) {
|
||||||
|
// Implementasi untuk menghapus inventaris
|
||||||
|
// Di implementasi nyata, akan memanggil Supabase untuk menghapus data inventaris
|
||||||
|
// await _supabaseService.deleteInventory(id);
|
||||||
|
|
||||||
|
// Perbarui data lokal
|
||||||
|
loadInventarisData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void logout() {
|
||||||
|
_authController.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeTab(int index) {
|
||||||
|
activeTabIndex.value = index;
|
||||||
|
}
|
||||||
|
}
|
187
lib/app/modules/petugas_desa/views/dashboard_view.dart
Normal file
187
lib/app/modules/petugas_desa/views/dashboard_view.dart
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/greeting_header.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/progress_section.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/schedule_card.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
import 'package:penyaluran_app/app/widgets/statistic_card.dart';
|
||||||
|
|
||||||
|
class DashboardView extends GetView<PetugasDesaController> {
|
||||||
|
const DashboardView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Header dengan greeting
|
||||||
|
GreetingHeader(
|
||||||
|
name: controller.roleData.value?['namaLengkap'] ?? 'Ahmad',
|
||||||
|
role: 'Petugas Desa',
|
||||||
|
desa: controller.roleData.value?['Desa'] ?? 'Jatihurip',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Jadwal penyaluran hari ini
|
||||||
|
ScheduleCard(
|
||||||
|
title: 'Jadwal Penyaluran Hari ini',
|
||||||
|
location: 'Kantor Kepala Desa (Beras)',
|
||||||
|
dateTime: '15 April 2023, 13:00 - 14:00',
|
||||||
|
isToday: true,
|
||||||
|
onTap: () => Get.toNamed('/petugas-desa/jadwal'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Jadwal penyaluran mendatang
|
||||||
|
ScheduleCard(
|
||||||
|
title: 'Jadwal Penyaluran Mendatang',
|
||||||
|
location: 'Balai Desa A (Sembako)',
|
||||||
|
dateTime: '17 April 2023, 13:00 - 14:00',
|
||||||
|
isToday: false,
|
||||||
|
onTap: () => Get.toNamed('/petugas-desa/jadwal'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Statistik penyaluran
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: StatisticCard(
|
||||||
|
title: 'Penitipan',
|
||||||
|
count: '3',
|
||||||
|
subtitle: 'Perlu Konfirmasi',
|
||||||
|
height: 120,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: StatisticCard(
|
||||||
|
title: 'Penjadwalan',
|
||||||
|
count: '1',
|
||||||
|
subtitle: 'Perlu Konfirmasi',
|
||||||
|
height: 120,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: StatisticCard(
|
||||||
|
title: 'Pengaduan',
|
||||||
|
count: '1',
|
||||||
|
subtitle: 'Perlu Tindakan',
|
||||||
|
height: 120,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Progress penyaluran
|
||||||
|
ProgressSection(
|
||||||
|
progressValue: 0.7,
|
||||||
|
total: 100,
|
||||||
|
distributed: 70,
|
||||||
|
scheduled: 20,
|
||||||
|
unscheduled: 10,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Daftar penerima
|
||||||
|
_buildRecipientsList(textTheme),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRecipientsList(TextTheme textTheme) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Daftar Penerima',
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Lihat Semua',
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
size: 16,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_buildRecipientItem(
|
||||||
|
'Siti Rahayu', '3201020107030010', 'Selesai', textTheme),
|
||||||
|
_buildRecipientItem(
|
||||||
|
'Budi Santoso', '3201020107030011', 'Selesai', textTheme),
|
||||||
|
_buildRecipientItem(
|
||||||
|
'Dewi Lestari', '3201020107030012', 'Selesai', textTheme),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRecipientItem(
|
||||||
|
String name, String nik, String status, TextTheme textTheme) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppTheme.primaryGradient,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(
|
||||||
|
name,
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
'NIK: $nik',
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
status,
|
||||||
|
style: textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
389
lib/app/modules/petugas_desa/views/inventaris_view.dart
Normal file
389
lib/app/modules/petugas_desa/views/inventaris_view.dart
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class InventarisView extends GetView<PetugasDesaController> {
|
||||||
|
const InventarisView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Ringkasan inventaris
|
||||||
|
_buildInventarisSummary(context),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Filter dan pencarian
|
||||||
|
_buildFilterSearch(context),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Daftar inventaris
|
||||||
|
_buildInventarisList(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInventarisSummary(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Ringkasan Inventaris',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildSummaryItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.inventory_2_outlined,
|
||||||
|
title: 'Total Stok',
|
||||||
|
value: '1,250 kg',
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildSummaryItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.input,
|
||||||
|
title: 'Masuk Bulan Ini',
|
||||||
|
value: '500 kg',
|
||||||
|
color: Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildSummaryItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.output,
|
||||||
|
title: 'Keluar Bulan Ini',
|
||||||
|
value: '350 kg',
|
||||||
|
color: Colors.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSummaryItem(
|
||||||
|
BuildContext context, {
|
||||||
|
required IconData icon,
|
||||||
|
required String title,
|
||||||
|
required String value,
|
||||||
|
required Color color,
|
||||||
|
}) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.2),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: color,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFilterSearch(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Cari bantuan...',
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade100,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan dialog filter
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.filter_list),
|
||||||
|
tooltip: 'Filter',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInventarisList(BuildContext context) {
|
||||||
|
final List<Map<String, dynamic>> inventarisList = [
|
||||||
|
{
|
||||||
|
'nama': 'Beras',
|
||||||
|
'jenis': 'Sembako',
|
||||||
|
'stok': '750 kg',
|
||||||
|
'lokasi': 'Gudang Utama',
|
||||||
|
'tanggal_masuk': '10 April 2023',
|
||||||
|
'kadaluarsa': '10 April 2024',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'nama': 'Minyak Goreng',
|
||||||
|
'jenis': 'Sembako',
|
||||||
|
'stok': '250 liter',
|
||||||
|
'lokasi': 'Gudang Utama',
|
||||||
|
'tanggal_masuk': '12 April 2023',
|
||||||
|
'kadaluarsa': '12 Oktober 2023',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'nama': 'Paket Sembako',
|
||||||
|
'jenis': 'Paket Bantuan',
|
||||||
|
'stok': '100 paket',
|
||||||
|
'lokasi': 'Gudang Cabang',
|
||||||
|
'tanggal_masuk': '15 April 2023',
|
||||||
|
'kadaluarsa': '15 Juli 2023',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'nama': 'Selimut',
|
||||||
|
'jenis': 'Non-Pangan',
|
||||||
|
'stok': '150 buah',
|
||||||
|
'lokasi': 'Gudang Cabang',
|
||||||
|
'tanggal_masuk': '5 April 2023',
|
||||||
|
'kadaluarsa': '-',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Daftar Inventaris',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// Navigasi ke halaman tambah inventaris
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Tambah'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
...inventarisList.map((item) => _buildInventarisItem(context, item)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInventarisItem(BuildContext context, Map<String, dynamic> item) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withAlpha(26),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item['nama'] ?? '',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
item['jenis'] ?? '',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.inventory,
|
||||||
|
label: 'Stok',
|
||||||
|
value: item['stok'] ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.location_on_outlined,
|
||||||
|
label: 'Lokasi',
|
||||||
|
value: item['lokasi'] ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.calendar_today,
|
||||||
|
label: 'Tanggal Masuk',
|
||||||
|
value: item['tanggal_masuk'] ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.timelapse,
|
||||||
|
label: 'Kadaluarsa',
|
||||||
|
value: item['kadaluarsa'] ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan detail inventaris
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.edit_outlined, size: 18),
|
||||||
|
label: const Text('Edit'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.blue,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan dialog konfirmasi hapus
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 18),
|
||||||
|
label: const Text('Hapus'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItemDetail(
|
||||||
|
BuildContext context, {
|
||||||
|
required IconData icon,
|
||||||
|
required String label,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
189
lib/app/modules/petugas_desa/views/jadwal_view.dart
Normal file
189
lib/app/modules/petugas_desa/views/jadwal_view.dart
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class JadwalView extends GetView<PetugasDesaController> {
|
||||||
|
const JadwalView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Jadwal hari ini
|
||||||
|
_buildJadwalSection(
|
||||||
|
textTheme,
|
||||||
|
title: 'Hari Ini',
|
||||||
|
jadwalList: [
|
||||||
|
{
|
||||||
|
'lokasi': 'Kantor Kepala Desa',
|
||||||
|
'jenisBantuan': 'Beras',
|
||||||
|
'tanggal': '15 April 2023',
|
||||||
|
'waktu': '13:00 - 14:00',
|
||||||
|
'status': 'Aktif',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Jadwal mendatang
|
||||||
|
_buildJadwalSection(
|
||||||
|
textTheme,
|
||||||
|
title: 'Mendatang',
|
||||||
|
jadwalList: [
|
||||||
|
{
|
||||||
|
'lokasi': 'Balai Desa A',
|
||||||
|
'jenisBantuan': 'Sembako',
|
||||||
|
'tanggal': '17 April 2023',
|
||||||
|
'waktu': '13:00 - 14:00',
|
||||||
|
'status': 'Terjadwal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lokasi': 'Balai Desa B',
|
||||||
|
'jenisBantuan': 'Uang Tunai',
|
||||||
|
'tanggal': '20 April 2023',
|
||||||
|
'waktu': '10:00 - 12:00',
|
||||||
|
'status': 'Terjadwal',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Jadwal selesai
|
||||||
|
_buildJadwalSection(
|
||||||
|
textTheme,
|
||||||
|
title: 'Selesai',
|
||||||
|
jadwalList: [
|
||||||
|
{
|
||||||
|
'lokasi': 'Kantor Kepala Desa',
|
||||||
|
'jenisBantuan': 'Beras',
|
||||||
|
'tanggal': '10 April 2023',
|
||||||
|
'waktu': '13:00 - 14:00',
|
||||||
|
'status': 'Selesai',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'lokasi': 'Balai Desa C',
|
||||||
|
'jenisBantuan': 'Sembako',
|
||||||
|
'tanggal': '5 April 2023',
|
||||||
|
'waktu': '09:00 - 11:00',
|
||||||
|
'status': 'Selesai',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildJadwalSection(
|
||||||
|
TextTheme textTheme, {
|
||||||
|
required String title,
|
||||||
|
required List<Map<String, String>> jadwalList,
|
||||||
|
}) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
...jadwalList.map((jadwal) => _buildJadwalItem(textTheme, jadwal)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildJadwalItem(TextTheme textTheme, Map<String, String> jadwal) {
|
||||||
|
Color statusColor;
|
||||||
|
switch (jadwal['status']) {
|
||||||
|
case 'Aktif':
|
||||||
|
statusColor = Colors.green;
|
||||||
|
break;
|
||||||
|
case 'Terjadwal':
|
||||||
|
statusColor = Colors.blue;
|
||||||
|
break;
|
||||||
|
case 'Selesai':
|
||||||
|
statusColor = Colors.grey;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusColor = Colors.orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withAlpha(26),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
jadwal['lokasi'] ?? '',
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: statusColor.withAlpha(26),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
jadwal['status'] ?? '',
|
||||||
|
style: textTheme.bodySmall?.copyWith(
|
||||||
|
color: statusColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Jenis Bantuan: ${jadwal['jenisBantuan'] ?? ''}',
|
||||||
|
style: textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Tanggal: ${jadwal['tanggal'] ?? ''}',
|
||||||
|
style: textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Waktu: ${jadwal['waktu'] ?? ''}',
|
||||||
|
style: textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
183
lib/app/modules/petugas_desa/views/notifikasi_view.dart
Normal file
183
lib/app/modules/petugas_desa/views/notifikasi_view.dart
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class NotifikasiView extends GetView<PetugasDesaController> {
|
||||||
|
const NotifikasiView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Notifikasi hari ini
|
||||||
|
_buildNotifikasiSection(
|
||||||
|
textTheme,
|
||||||
|
title: 'Hari Ini',
|
||||||
|
notifikasiList: [
|
||||||
|
{
|
||||||
|
'judul': 'Jadwal Penyaluran Baru',
|
||||||
|
'pesan':
|
||||||
|
'Jadwal penyaluran beras telah ditambahkan untuk hari ini',
|
||||||
|
'waktu': '08:30',
|
||||||
|
'dibaca': false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'judul': 'Pengajuan Bantuan Baru',
|
||||||
|
'pesan':
|
||||||
|
'Ada 3 pengajuan bantuan baru yang perlu diverifikasi',
|
||||||
|
'waktu': '10:15',
|
||||||
|
'dibaca': false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Notifikasi kemarin
|
||||||
|
_buildNotifikasiSection(
|
||||||
|
textTheme,
|
||||||
|
title: 'Kemarin',
|
||||||
|
notifikasiList: [
|
||||||
|
{
|
||||||
|
'judul': 'Laporan Penyaluran',
|
||||||
|
'pesan':
|
||||||
|
'Laporan penyaluran bantuan tanggal 14 April 2023 telah selesai',
|
||||||
|
'waktu': '16:45',
|
||||||
|
'dibaca': true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'judul': 'Pengaduan Warga',
|
||||||
|
'pesan':
|
||||||
|
'Ada pengaduan baru dari warga yang perlu ditindaklanjuti',
|
||||||
|
'waktu': '14:20',
|
||||||
|
'dibaca': true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Notifikasi minggu ini
|
||||||
|
_buildNotifikasiSection(
|
||||||
|
textTheme,
|
||||||
|
title: 'Minggu Ini',
|
||||||
|
notifikasiList: [
|
||||||
|
{
|
||||||
|
'judul': 'Perubahan Jadwal',
|
||||||
|
'pesan':
|
||||||
|
'Jadwal penyaluran bantuan di Balai Desa A diubah menjadi tanggal 17 April 2023',
|
||||||
|
'waktu': 'Sen, 13:00',
|
||||||
|
'dibaca': true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'judul': 'Donasi Baru',
|
||||||
|
'pesan':
|
||||||
|
'PT Sejahtera telah mengirimkan donasi baru berupa sembako',
|
||||||
|
'waktu': 'Sen, 09:30',
|
||||||
|
'dibaca': true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNotifikasiSection(
|
||||||
|
TextTheme textTheme, {
|
||||||
|
required String title,
|
||||||
|
required List<Map<String, dynamic>> notifikasiList,
|
||||||
|
}) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
...notifikasiList
|
||||||
|
.map((notifikasi) => _buildNotifikasiItem(textTheme, notifikasi)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNotifikasiItem(
|
||||||
|
TextTheme textTheme, Map<String, dynamic> notifikasi) {
|
||||||
|
final bool dibaca = notifikasi['dibaca'] as bool;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: dibaca ? Colors.white : Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withAlpha(26),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (!dibaca)
|
||||||
|
Container(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
margin: const EdgeInsets.only(top: 5, right: 10),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
notifikasi['judul'] ?? '',
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
notifikasi['waktu'] ?? '',
|
||||||
|
style: textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
notifikasi['pesan'] ?? '',
|
||||||
|
style: textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
315
lib/app/modules/petugas_desa/views/petugas_desa_view.dart
Normal file
315
lib/app/modules/petugas_desa/views/petugas_desa_view.dart
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/dashboard_view.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/jadwal_view.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/notifikasi_view.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/inventaris_view.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||||
|
const PetugasDesaView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
key: scaffoldKey,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Obx(() {
|
||||||
|
switch (controller.activeTabIndex.value) {
|
||||||
|
case 0:
|
||||||
|
return const Text('Dashboard');
|
||||||
|
case 1:
|
||||||
|
return const Text('Jadwal Penyaluran');
|
||||||
|
case 2:
|
||||||
|
return const Text('Notifikasi');
|
||||||
|
case 3:
|
||||||
|
return const Text('Inventaris');
|
||||||
|
default:
|
||||||
|
return const Text('Petugas Desa');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {
|
||||||
|
scaffoldKey.currentState?.openDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
// Tombol aksi berdasarkan tab yang aktif
|
||||||
|
Obx(() {
|
||||||
|
final activeTab = controller.activeTabIndex.value;
|
||||||
|
|
||||||
|
// Tombol notifikasi selalu ditampilkan
|
||||||
|
final notificationButton = Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.notifications_outlined),
|
||||||
|
onPressed: () {
|
||||||
|
controller.changeTab(2); // Pindah ke tab notifikasi
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (controller.jumlahNotifikasiBelumDibaca.value > 0)
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 16,
|
||||||
|
minHeight: 16,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
controller.jumlahNotifikasiBelumDibaca.value.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tombol tambah untuk jadwal dan inventaris
|
||||||
|
if (activeTab == 1) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
tooltip: 'Tambah Jadwal',
|
||||||
|
onPressed: () {
|
||||||
|
// Implementasi untuk menambah jadwal baru
|
||||||
|
},
|
||||||
|
),
|
||||||
|
notificationButton,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else if (activeTab == 2) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.check_circle_outline),
|
||||||
|
tooltip: 'Tandai Semua Dibaca',
|
||||||
|
onPressed: () {
|
||||||
|
// Implementasi untuk menandai semua notifikasi sebagai dibaca
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (activeTab == 3) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
tooltip: 'Tambah Inventaris',
|
||||||
|
onPressed: () {
|
||||||
|
// Implementasi untuk menambah inventaris baru
|
||||||
|
},
|
||||||
|
),
|
||||||
|
notificationButton,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return notificationButton;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
drawer: _buildDrawer(context),
|
||||||
|
body: Obx(() {
|
||||||
|
switch (controller.activeTabIndex.value) {
|
||||||
|
case 0:
|
||||||
|
return const DashboardView();
|
||||||
|
case 1:
|
||||||
|
return const JadwalView();
|
||||||
|
case 2:
|
||||||
|
return const NotifikasiView();
|
||||||
|
case 3:
|
||||||
|
return const InventarisView();
|
||||||
|
default:
|
||||||
|
return const DashboardView();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
bottomNavigationBar: _buildBottomNavigationBar(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDrawer(BuildContext context) {
|
||||||
|
return Drawer(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: [
|
||||||
|
DrawerHeader(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppTheme.primaryGradient,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
const CircleAvatar(
|
||||||
|
radius: 30,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
child: Icon(
|
||||||
|
Icons.person,
|
||||||
|
size: 40,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
controller.nama,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Petugas Desa',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withOpacity(0.8),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.dashboard_outlined),
|
||||||
|
title: const Text('Dashboard'),
|
||||||
|
selected: controller.activeTabIndex.value == 0,
|
||||||
|
selectedColor: AppTheme.primaryColor,
|
||||||
|
onTap: () {
|
||||||
|
controller.changeTab(0);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.calendar_today_outlined),
|
||||||
|
title: const Text('Jadwal Penyaluran'),
|
||||||
|
selected: controller.activeTabIndex.value == 1,
|
||||||
|
selectedColor: AppTheme.primaryColor,
|
||||||
|
onTap: () {
|
||||||
|
controller.changeTab(1);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
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'),
|
||||||
|
selected: controller.activeTabIndex.value == 2,
|
||||||
|
selectedColor: AppTheme.primaryColor,
|
||||||
|
onTap: () {
|
||||||
|
controller.changeTab(2);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.inventory_2_outlined),
|
||||||
|
title: const Text('Inventaris'),
|
||||||
|
selected: controller.activeTabIndex.value == 3,
|
||||||
|
selectedColor: AppTheme.primaryColor,
|
||||||
|
onTap: () {
|
||||||
|
controller.changeTab(3);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.person_outline),
|
||||||
|
title: const Text('Profil'),
|
||||||
|
onTap: () {
|
||||||
|
// Navigasi ke halaman profil
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.settings_outlined),
|
||||||
|
title: const Text('Pengaturan'),
|
||||||
|
onTap: () {
|
||||||
|
// Navigasi ke halaman pengaturan
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.logout),
|
||||||
|
title: const Text('Keluar'),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
controller.logout();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBottomNavigationBar() {
|
||||||
|
return Obx(() => BottomNavigationBar(
|
||||||
|
currentIndex: controller.activeTabIndex.value,
|
||||||
|
onTap: controller.changeTab,
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
selectedItemColor: AppTheme.primaryColor,
|
||||||
|
unselectedItemColor: Colors.grey,
|
||||||
|
items: const [
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.dashboard_outlined),
|
||||||
|
activeIcon: Icon(Icons.dashboard),
|
||||||
|
label: 'Dashboard',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.calendar_today_outlined),
|
||||||
|
activeIcon: Icon(Icons.calendar_today),
|
||||||
|
label: 'Jadwal',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.notifications_outlined),
|
||||||
|
activeIcon: Icon(Icons.notifications),
|
||||||
|
label: 'Notifikasi',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.inventory_2_outlined),
|
||||||
|
activeIcon: Icon(Icons.inventory_2),
|
||||||
|
label: 'Inventaris',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,12 @@ import 'package:penyaluran_app/app/modules/auth/views/login_view.dart';
|
|||||||
import 'package:penyaluran_app/app/modules/home/views/home_view.dart';
|
import 'package:penyaluran_app/app/modules/home/views/home_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/dashboard/views/warga_dashboard_view.dart';
|
import 'package:penyaluran_app/app/modules/dashboard/views/warga_dashboard_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/dashboard/views/petugas_verifikasi_dashboard_view.dart';
|
import 'package:penyaluran_app/app/modules/dashboard/views/petugas_verifikasi_dashboard_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/dashboard/views/petugas_desa_dashboard_view.dart';
|
|
||||||
import 'package:penyaluran_app/app/modules/dashboard/views/donatur_dashboard_view.dart';
|
import 'package:penyaluran_app/app/modules/dashboard/views/donatur_dashboard_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/auth/bindings/auth_binding.dart';
|
import 'package:penyaluran_app/app/modules/auth/bindings/auth_binding.dart';
|
||||||
import 'package:penyaluran_app/app/modules/home/bindings/home_binding.dart';
|
import 'package:penyaluran_app/app/modules/home/bindings/home_binding.dart';
|
||||||
import 'package:penyaluran_app/app/modules/dashboard/bindings/dashboard_binding.dart';
|
import 'package:penyaluran_app/app/modules/dashboard/bindings/dashboard_binding.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/petugas_desa_view.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/petugas_desa_binding.dart';
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
|
|
||||||
@ -39,8 +40,8 @@ class AppPages {
|
|||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.petugasDesaDashboard,
|
name: _Paths.petugasDesaDashboard,
|
||||||
page: () => const PetugasDesaDashboardView(),
|
page: () => const PetugasDesaView(),
|
||||||
binding: DashboardBinding(),
|
binding: PetugasDesaBinding(),
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.donaturDashboard,
|
name: _Paths.donaturDashboard,
|
||||||
|
Reference in New Issue
Block a user