import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:get/get.dart'; import 'package:on_field_work/controller/attendance/attendance_screen_controller.dart'; import 'package:on_field_work/controller/dashboard/dashboard_controller.dart'; import 'package:on_field_work/controller/dynamicMenu/dynamic_menu_controller.dart'; import 'package:on_field_work/controller/project_controller.dart'; import 'package:on_field_work/helpers/services/storage/local_storage.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; import 'package:on_field_work/helpers/utils/permission_constants.dart'; import 'package:on_field_work/helpers/widgets/avatar.dart'; import 'package:on_field_work/helpers/widgets/dashbaord/expense_breakdown_chart.dart'; import 'package:on_field_work/helpers/widgets/dashbaord/expense_by_status_widget.dart'; import 'package:on_field_work/helpers/widgets/dashbaord/monthly_expense_dashboard_chart.dart'; import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart'; import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/model/attendance/attendence_action_button.dart'; import 'package:on_field_work/model/attendance/log_details_view.dart'; import 'package:on_field_work/view/layouts/layout.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State with UIMixin { final DashboardController dashboardController = Get.put(DashboardController(), permanent: true); final AttendanceController attendanceController = Get.put(AttendanceController()); final DynamicMenuController menuController = Get.put(DynamicMenuController()); final ProjectController projectController = Get.find(); bool hasMpin = true; @override void initState() { super.initState(); _checkMpinStatus(); } Future _checkMpinStatus() async { hasMpin = await LocalStorage.getIsMpin(); if (mounted) { setState(() {}); } } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- Widget _cardWrapper({required Widget child}) { return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.black12.withOpacity(.04)), boxShadow: [ BoxShadow( color: Colors.black12.withOpacity(.05), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: child, ); } Widget _sectionTitle(String title) { return Padding( padding: const EdgeInsets.only(left: 4, bottom: 8), child: Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.black87, ), ), ); } // --------------------------------------------------------------------------- // Quick Actions // --------------------------------------------------------------------------- Widget _quickActions() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _sectionTitle('Quick Action'), Obx(() { if (dashboardController.isLoadingEmployees.value) { // Show loading skeleton return SkeletonLoaders.attendanceQuickCardSkeleton(); } final employees = dashboardController.employees; final employee = employees.isNotEmpty ? employees.first : null; if (employee == null) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), gradient: LinearGradient( colors: [ contentTheme.primary.withOpacity(0.3), contentTheme.primary.withOpacity(0.6), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: const Text( 'No attendance data available', style: TextStyle(color: Colors.white), ), ); } // Actual employee quick action card final bool isCheckedIn = employee.checkIn != null; final bool isCheckedOut = employee.checkOut != null; final String statusText = !isCheckedIn ? 'Check In Pending' : isCheckedIn && !isCheckedOut ? 'Checked In' : 'Checked Out'; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), gradient: LinearGradient( colors: [ contentTheme.primary.withOpacity(0.3), contentTheme.primary.withOpacity(0.6), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Avatar( firstName: employee.firstName, lastName: employee.lastName, size: 30, ), MySpacing.width(10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleSmall( employee.name, fontWeight: 600, color: Colors.white, ), MyText.labelSmall( employee.designation, fontWeight: 500, color: Colors.white70, ), ], ), ), MyText.bodySmall( statusText, fontWeight: 600, color: Colors.white, ), ], ), const SizedBox(height: 12), Text( !isCheckedIn ? 'You are not checked-in yet. Please check-in to start your work.' : !isCheckedOut ? 'You are currently checked-in. Don\'t forget to check-out after your work.' : 'You have checked-out for today.', style: const TextStyle( color: Colors.white70, fontSize: 13, ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ AttendanceActionButton( employee: employee, attendanceController: attendanceController, ), if (isCheckedIn) ...[ MySpacing.width(8), AttendanceLogViewButton( employee: employee, attendanceController: attendanceController, ), ], ], ), ], ), ); }), ], ); } // --------------------------------------------------------------------------- // Dashboard Modules // --------------------------------------------------------------------------- Widget _dashboardModules() { return Obx(() { if (menuController.isLoading.value) { return SkeletonLoaders.dashboardCardsSkeleton( maxWidth: MediaQuery.of(context).size.width, ); } final bool projectSelected = projectController.selectedProject != null; // these are String constants from permission_constants.dart final List cardOrder = [ MenuItems.attendance, MenuItems.employees, MenuItems.directory, MenuItems.finance, MenuItems.documents, MenuItems.serviceProjects, MenuItems.infraProjects, ]; final Map meta = { MenuItems.attendance: _DashboardCardMeta(LucideIcons.scan_face, contentTheme.success), MenuItems.employees: _DashboardCardMeta(LucideIcons.users, contentTheme.warning), MenuItems.directory: _DashboardCardMeta(LucideIcons.folder, contentTheme.info), MenuItems.finance: _DashboardCardMeta(LucideIcons.wallet, contentTheme.info), MenuItems.documents: _DashboardCardMeta(LucideIcons.file_text, contentTheme.info), MenuItems.serviceProjects: _DashboardCardMeta(LucideIcons.package, contentTheme.info), MenuItems.infraProjects: _DashboardCardMeta(LucideIcons.building_2, contentTheme.primary), }; final Map allowed = { for (final m in menuController.menuItems) if (m.available && meta.containsKey(m.id)) m.id: m, }; final List filtered = cardOrder.where((id) => allowed.containsKey(id)).toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 4, bottom: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Modules', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.black87, ), ), if (!projectSelected) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(4), ), child: const Text( 'Select Project', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w600, color: Colors.grey, ), ), ), ], ), ), GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 2), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 15, mainAxisSpacing: 8, childAspectRatio: 1.8, ), itemCount: filtered.length, itemBuilder: (context, index) { final String id = filtered[index]; final item = allowed[id]!; final _DashboardCardMeta cardMeta = meta[id]!; final bool isEnabled = item.name == 'Attendance' ? true : projectSelected; return GestureDetector( onTap: () { if (!isEnabled) { Get.snackbar( 'Required', 'Please select a project first', snackPosition: SnackPosition.BOTTOM, margin: const EdgeInsets.all(16), backgroundColor: Colors.black87, colorText: Colors.white, duration: const Duration(seconds: 2), ); } else { Get.toNamed(item.mobileLink); } }, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), border: Border.all( color: isEnabled ? Colors.black12.withOpacity(0.06) : Colors.transparent, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( cardMeta.icon, size: 20, color: isEnabled ? cardMeta.color : Colors.grey.shade300, ), const SizedBox(height: 6), Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: Text( item.name, textAlign: TextAlign.center, style: TextStyle( fontSize: 10, fontWeight: isEnabled ? FontWeight.w600 : FontWeight.w400, color: isEnabled ? Colors.black87 : Colors.grey.shade400, height: 1.2, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ), ); }, ), ], ); }); } // --------------------------------------------------------------------------- // Project Selector // --------------------------------------------------------------------------- Widget _projectSelector() { return Obx(() { final bool isLoading = projectController.isLoading.value; final bool expanded = projectController.isProjectSelectionExpanded.value; final projects = projectController.projects; final String? selectedId = projectController.selectedProjectId.value; if (isLoading) { return SkeletonLoaders.dashboardCardsSkeleton( maxWidth: MediaQuery.of(context).size.width, ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _sectionTitle('Project'), GestureDetector( onTap: () => projectController.isProjectSelectionExpanded.toggle(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.black12.withOpacity(.15)), boxShadow: [ BoxShadow( color: Colors.black12.withOpacity(.04), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( children: [ const Icon( Icons.work_outline, color: Colors.blue, size: 20, ), const SizedBox(width: 12), Expanded( child: Text( projects .firstWhereOrNull( (p) => p.id == selectedId, ) ?.name ?? 'Select Project', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), ), Icon( expanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, size: 26, color: Colors.black54, ), ], ), ), ), if (expanded) _projectDropdownList(projects, selectedId), ], ); }); } Widget _projectDropdownList(List projects, String? selectedId) { return Container( margin: const EdgeInsets.only(top: 10), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.black12.withOpacity(.2)), boxShadow: [ BoxShadow( color: Colors.black12.withOpacity(.07), blurRadius: 10, offset: const Offset(0, 3), ), ], ), constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.33, ), child: Column( children: [ TextField( decoration: InputDecoration( hintText: 'Search project...', isDense: true, prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), ), ), ), const SizedBox(height: 10), Expanded( child: ListView.builder( itemCount: projects.length, itemBuilder: (_, index) { final project = projects[index]; return RadioListTile( dense: true, value: project.id, groupValue: selectedId, onChanged: (value) { if (value != null) { projectController.updateSelectedProject(value); projectController.isProjectSelectionExpanded.value = false; } }, title: Text(project.name), ); }, ), ), ], ), ); } // --------------------------------------------------------------------------- // Build // --------------------------------------------------------------------------- @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xfff5f6fa), body: Layout( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _projectSelector(), MySpacing.height(20), _quickActions(), MySpacing.height(20), _dashboardModules(), MySpacing.height(20), _sectionTitle('Reports & Analytics'), _cardWrapper( child: ExpenseTypeReportChart(), ), _cardWrapper( child: ExpenseByStatusWidget( controller: dashboardController, ), ), _cardWrapper( child: MonthlyExpenseDashboardChart(), ), MySpacing.height(20), ], ), ), ), ); } } class _DashboardCardMeta { final IconData icon; final Color color; const _DashboardCardMeta(this.icon, this.color); }