import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:get/get.dart'; import 'package:marco/controller/dashboard/dashboard_controller.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/controller/dynamicMenu/dynamic_menu_controller.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/my_card.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/dashbaord/attendance_overview_chart.dart'; import 'package:marco/helpers/widgets/dashbaord/project_progress_chart.dart'; import 'package:marco/helpers/widgets/dashbaord/expense_breakdown_chart.dart'; import 'package:marco/helpers/widgets/dashbaord/expense_by_status_widget.dart'; import 'package:marco/helpers/widgets/dashbaord/monthly_expense_dashboard_chart.dart'; import 'package:marco/helpers/widgets/dashbaord/dashboard_overview_widgets.dart'; import 'package:marco/view/layouts/layout.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/widgets/my_custom_skeleton.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 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(() {}); } @override Widget build(BuildContext context) { return Layout( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDashboardCards(), MySpacing.height(24), _buildAttendanceChartSection(), MySpacing.height(24), _buildProjectProgressChartSection(), MySpacing.height(24), SizedBox( width: double.infinity, child: DashboardOverviewWidgets.teamsOverview(), ), MySpacing.height(24), SizedBox( width: double.infinity, child: DashboardOverviewWidgets.tasksOverview(), ), MySpacing.height(24), ExpenseByStatusWidget(controller: dashboardController), MySpacing.height(24), ExpenseTypeReportChart(), MySpacing.height(24), MonthlyExpenseDashboardChart(), ], ), ), ); } /// ---------------- Dynamic Dashboard Cards ---------------- Widget _buildDashboardCards() { return Obx(() { if (menuController.isLoading.value) { return SkeletonLoaders.dashboardCardsSkeleton(); } if (menuController.hasError.value || menuController.menuItems.isEmpty) { return const Center( child: Text( "Failed to load menus. Please try again later.", style: TextStyle(color: Colors.red), ), ); } final projectSelected = projectController.selectedProject != null; // Define dashboard card meta with order final List cardOrder = [ MenuItems.attendance, MenuItems.employees, MenuItems.dailyTaskPlanning, MenuItems.dailyProgressReport, MenuItems.directory, MenuItems.finance, MenuItems.documents, MenuItems.serviceProjects ]; final Map cardMeta = { MenuItems.attendance: _DashboardCardMeta(LucideIcons.scan_face, contentTheme.success), MenuItems.employees: _DashboardCardMeta(LucideIcons.users, contentTheme.warning), MenuItems.dailyTaskPlanning: _DashboardCardMeta(LucideIcons.logs, contentTheme.info), MenuItems.dailyProgressReport: _DashboardCardMeta(LucideIcons.list_todo, contentTheme.info), 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), }; // Filter only available menus that exist in cardMeta final allowedMenusMap = { for (var menu in menuController.menuItems) if (menu.available && cardMeta.containsKey(menu.id)) menu.id: menu }; if (allowedMenusMap.isEmpty) { return const Center( child: Text( "No accessible modules found.", style: TextStyle(color: Colors.grey), ), ); } // Create list of cards in fixed order final stats = cardOrder.where((id) => allowedMenusMap.containsKey(id)).map((id) { final menu = allowedMenusMap[id]!; final meta = cardMeta[id]!; return _DashboardStatItem( meta.icon, menu.name, meta.color, menu.mobileLink); }).toList(); return LayoutBuilder(builder: (context, constraints) { int crossAxisCount = (constraints.maxWidth ~/ 80).clamp(2, 4); double cardWidth = (constraints.maxWidth - (crossAxisCount - 1) * 6) / crossAxisCount; return Wrap( spacing: 6, runSpacing: 6, alignment: WrapAlignment.start, children: stats .map((stat) => _buildDashboardCard(stat, projectSelected, cardWidth)) .toList(), ); }); }); } Widget _buildDashboardCard( _DashboardStatItem stat, bool isProjectSelected, double width) { final isEnabled = stat.title == "Attendance" ? true : isProjectSelected; return Opacity( opacity: isEnabled ? 1.0 : 0.4, child: IgnorePointer( ignoring: !isEnabled, child: InkWell( onTap: () => _onDashboardCardTap(stat, isEnabled), borderRadius: BorderRadius.circular(5), child: MyCard.bordered( width: width, height: 60, paddingAll: 4, borderRadiusAll: 5, border: Border.all(color: Colors.grey.withOpacity(0.15)), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: stat.color.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Icon( stat.icon, size: 16, color: stat.color, ), ), MySpacing.height(4), Flexible( child: Text( stat.title, textAlign: TextAlign.center, style: const TextStyle( fontSize: 10, overflow: TextOverflow.ellipsis, ), maxLines: 2, ), ), ], ), ), ), ), ); } void _onDashboardCardTap(_DashboardStatItem statItem, bool isEnabled) { if (!isEnabled) { Get.defaultDialog( title: "No Project Selected", middleText: "Please select a project before accessing this module.", confirm: ElevatedButton( onPressed: () => Get.back(), child: const Text("OK"), ), ); } else { Get.toNamed(statItem.route); } } /// ---------------- Project Progress Chart ---------------- Widget _buildProjectProgressChartSection() { return Obx(() { if (dashboardController.projectChartData.isEmpty) { return const Padding( padding: EdgeInsets.all(16), child: Center( child: Text("No project progress data available."), ), ); } return ClipRRect( borderRadius: BorderRadius.circular(5), child: SizedBox( height: 400, child: ProjectProgressChart( data: dashboardController.projectChartData, ), ), ); }); } /// ---------------- Attendance Chart ---------------- Widget _buildAttendanceChartSection() { return Obx(() { final attendanceMenu = menuController.menuItems .firstWhereOrNull((m) => m.id == MenuItems.attendance); if (attendanceMenu == null || !attendanceMenu.available) return const SizedBox.shrink(); final isProjectSelected = projectController.selectedProject != null; return Opacity( opacity: isProjectSelected ? 1.0 : 0.4, child: IgnorePointer( ignoring: !isProjectSelected, child: ClipRRect( borderRadius: BorderRadius.circular(5), child: SizedBox( height: 400, child: AttendanceDashboardChart(), ), ), ), ); }); } } /// ---------------- Dashboard Card Models ---------------- class _DashboardStatItem { final IconData icon; final String title; final Color color; final String route; _DashboardStatItem(this.icon, this.title, this.color, this.route); } class _DashboardCardMeta { final IconData icon; final Color color; _DashboardCardMeta(this.icon, this.color); }