diff --git a/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart b/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart deleted file mode 100644 index dc800a3..0000000 --- a/lib/app/modules/dashboard/views/petugas_desa_dashboard_view.dart +++ /dev/null @@ -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 { - const PetugasDesaDashboardView({super.key}); - - @override - Widget build(BuildContext context) { - final GlobalKey scaffoldKey = GlobalKey(); - 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(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> 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: () {}, - ), - ), - ); - } -} diff --git a/lib/app/modules/petugas_desa/bindings/petugas_desa_binding.dart b/lib/app/modules/petugas_desa/bindings/petugas_desa_binding.dart new file mode 100644 index 0000000..18a78fb --- /dev/null +++ b/lib/app/modules/petugas_desa/bindings/petugas_desa_binding.dart @@ -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(), + ); + } +} diff --git a/lib/app/modules/petugas_desa/components/greeting_header.dart b/lib/app/modules/petugas_desa/components/greeting_header.dart new file mode 100644 index 0000000..be7268b --- /dev/null +++ b/lib/app/modules/petugas_desa/components/greeting_header.dart @@ -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], + ), + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/components/progress_section.dart b/lib/app/modules/petugas_desa/components/progress_section.dart new file mode 100644 index 0000000..a50300f --- /dev/null +++ b/lib/app/modules/petugas_desa/components/progress_section.dart @@ -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(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, + ), + ), + ], + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/components/schedule_card.dart b/lib/app/modules/petugas_desa/components/schedule_card.dart new file mode 100644 index 0000000..547f46a --- /dev/null +++ b/lib/app/modules/petugas_desa/components/schedule_card.dart @@ -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, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart b/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart new file mode 100644 index 0000000..829d6b1 --- /dev/null +++ b/lib/app/modules/petugas_desa/controllers/petugas_desa_controller.dart @@ -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(); + final SupabaseService _supabaseService = SupabaseService.to; + + final RxBool isLoading = false.obs; + final Rx?> roleData = Rx?>(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> jadwalHariIni = + >[].obs; + final RxList> jadwalMendatang = + >[].obs; + final RxList> jadwalSelesai = + >[].obs; + + // Data untuk notifikasi + final RxList> notifikasiBelumDibaca = + >[].obs; + final RxInt jumlahNotifikasiBelumDibaca = 0.obs; + + // Data untuk inventaris + final RxList> daftarInventaris = + >[].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 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 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 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 loadNotifikasiData() async { + try { + // Simulasi data untuk notifikasi + await Future.delayed(const Duration(milliseconds: 500)); + + // Hitung jumlah notifikasi yang belum dibaca + final List> 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 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 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; + } +} diff --git a/lib/app/modules/petugas_desa/views/dashboard_view.dart b/lib/app/modules/petugas_desa/views/dashboard_view.dart new file mode 100644 index 0000000..4775585 --- /dev/null +++ b/lib/app/modules/petugas_desa/views/dashboard_view.dart @@ -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 { + 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, + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/views/inventaris_view.dart b/lib/app/modules/petugas_desa/views/inventaris_view.dart new file mode 100644 index 0000000..77a5a01 --- /dev/null +++ b/lib/app/modules/petugas_desa/views/inventaris_view.dart @@ -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 { + 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> 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 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, + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/app/modules/petugas_desa/views/jadwal_view.dart b/lib/app/modules/petugas_desa/views/jadwal_view.dart new file mode 100644 index 0000000..643c07e --- /dev/null +++ b/lib/app/modules/petugas_desa/views/jadwal_view.dart @@ -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 { + 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> 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 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, + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/views/notifikasi_view.dart b/lib/app/modules/petugas_desa/views/notifikasi_view.dart new file mode 100644 index 0000000..0f5e065 --- /dev/null +++ b/lib/app/modules/petugas_desa/views/notifikasi_view.dart @@ -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 { + 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> 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 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, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/petugas_desa/views/petugas_desa_view.dart b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart new file mode 100644 index 0000000..41e107e --- /dev/null +++ b/lib/app/modules/petugas_desa/views/petugas_desa_view.dart @@ -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 { + const PetugasDesaView({super.key}); + + @override + Widget build(BuildContext context) { + final GlobalKey scaffoldKey = GlobalKey(); + + 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', + ), + ], + )); + } +} diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index e547596..13433b3 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -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/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_desa_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/home/bindings/home_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'; @@ -39,8 +40,8 @@ class AppPages { ), GetPage( name: _Paths.petugasDesaDashboard, - page: () => const PetugasDesaDashboardView(), - binding: DashboardBinding(), + page: () => const PetugasDesaView(), + binding: PetugasDesaBinding(), ), GetPage( name: _Paths.donaturDashboard,