From 456df30c8e4b36b053437d3017b6c9ccde08df9d Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 12 Nov 2025 17:58:33 +0530 Subject: [PATCH] made chnages into dynamic menus --- lib/helpers/utils/permission_constants.dart | 49 +- lib/helpers/widgets/my_custom_skeleton.dart | 43 ++ lib/model/dynamicMenu/dynamic_menu_model.dart | 4 + lib/view/dashboard/dashboard_screen.dart | 457 +++++++----------- lib/view/finance/finance_screen.dart | 208 ++++---- 5 files changed, 398 insertions(+), 363 deletions(-) diff --git a/lib/helpers/utils/permission_constants.dart b/lib/helpers/utils/permission_constants.dart index f257cfc..733f6ab 100644 --- a/lib/helpers/utils/permission_constants.dart +++ b/lib/helpers/utils/permission_constants.dart @@ -25,14 +25,16 @@ class Permissions { // ------------------- Project Infrastructure -------------------------- /// Permission to manage project infrastructure (e.g., site details) - static const String manageProjectInfra = "cf2825ad-453b-46aa-91d9-27c124d63373"; + static const String manageProjectInfra = + "cf2825ad-453b-46aa-91d9-27c124d63373"; /// Permission to view infrastructure-related details static const String viewProjectInfra = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"; // ------------------- Attendance Management --------------------------- /// Permission to regularize (edit/update) attendance records - static const String regularizeAttendance = "57802c4a-00aa-4a1f-a048-fd2f70dd44b6"; + static const String regularizeAttendance = + "57802c4a-00aa-4a1f-a048-fd2f70dd44b6"; // ------------------- Task Management --------------------------------- /// Permission to create and manage tasks @@ -90,7 +92,8 @@ class Permissions { // ------------------- Application Roles ------------------------------- /// Application role ID for users with full expense management rights - static const String expenseManagement = "a4e25142-449b-4334-a6e5-22f70e4732d7"; + static const String expenseManagement = + "a4e25142-449b-4334-a6e5-22f70e4732d7"; // ------------------- Document Entities ------------------------------- /// Entity ID for project documents @@ -118,3 +121,43 @@ class Permissions { /// Permission to verify documents static const String verifyDocument = "13a1f30f-38d1-41bf-8e7a-b75189aab8e0"; } + +/// Contains constants for menu item IDs fetched from the sidebar menu API. +class MenuItems { + /// Dashboard menu + static const String dashboard = "29e03eda-03e8-4714-92fa-67ae0dc53202"; + + /// Daily Task Planning menu + static const String dailyTaskPlanning = + "77ac5205-f823-442e-b9e4-2420d658aa02"; + + /// Daily Progress Report menu + static const String dailyProgressReport = + "299e3cf5-d034-4403-b4a1-ea46d2714832"; + + /// Employees menu + static const String employees = "78f0206d-c6cc-44d0-832a-2031ed203018"; + + /// Attendance menu + static const String attendance = "2f212030-f36b-456c-8e7c-11f00f9ba42b"; + + /// Directory menu + static const String directory = "31bc367b-7c58-4604-95eb-da059a384103"; + + /// Expense & Reimbursement menu + static const String expenseReimbursement = + "0f0dc1a7-1aca-4cdb-9d7a-8a769ce40728"; + + /// Payment Requests menu + static const String paymentRequests = "b350a59f-2372-4f68-8dcf-f7cfc44523ca"; + + /// Advance Payment Statements menu + static const String advancePaymentStatements = + "e0251cc1-e6d9-417a-9c76-489cc4b6c347"; + + /// Finance menu + static const String finance = "5ac409dd-bbe0-4d56-bcb9-229bd3a6353c"; + + /// Documents menu + static const String documents = "92d2cc39-9e6a-46b2-ae50-84fbf83c95d3"; +} diff --git a/lib/helpers/widgets/my_custom_skeleton.dart b/lib/helpers/widgets/my_custom_skeleton.dart index b81ee63..5bb5309 100644 --- a/lib/helpers/widgets/my_custom_skeleton.dart +++ b/lib/helpers/widgets/my_custom_skeleton.dart @@ -33,6 +33,48 @@ class SkeletonLoaders { ); } +// Inside SkeletonLoaders class + static Widget dashboardCardsSkeleton({double? maxWidth}) { + return LayoutBuilder(builder: (context, constraints) { + double width = maxWidth ?? constraints.maxWidth; + int crossAxisCount = (width ~/ 80).clamp(2, 4); + double cardWidth = (width - (crossAxisCount - 1) * 6) / crossAxisCount; + + return Wrap( + spacing: 6, + runSpacing: 6, + children: List.generate(6, (index) { + return MyCard.bordered( + width: cardWidth, + height: 60, + paddingAll: 4, + borderRadiusAll: 5, + border: Border.all(color: Colors.grey.withOpacity(0.15)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), + ), + MySpacing.height(4), + Container( + width: cardWidth * 0.5, + height: 10, + color: Colors.grey.shade300, + ), + ], + ), + ); + }), + ); + }); + } + // Inside SkeletonLoaders class static Widget paymentRequestListSkeletonLoader() { return ListView.separated( @@ -256,6 +298,7 @@ class SkeletonLoaders { ), ); } + // Employee Detail Skeleton Loader static Widget employeeDetailSkeletonLoader() { return SingleChildScrollView( diff --git a/lib/model/dynamicMenu/dynamic_menu_model.dart b/lib/model/dynamicMenu/dynamic_menu_model.dart index 8348447..b70b699 100644 --- a/lib/model/dynamicMenu/dynamic_menu_model.dart +++ b/lib/model/dynamicMenu/dynamic_menu_model.dart @@ -59,11 +59,13 @@ class MenuItem { final String id; // Unique item ID final String name; // Display text final bool available; // Availability flag + final String mobileLink; // Mobile navigation link MenuItem({ required this.id, required this.name, required this.available, + required this.mobileLink, }); /// Creates MenuItem from JSON map @@ -72,6 +74,7 @@ class MenuItem { id: json['id'] as String? ?? '', name: json['name'] as String? ?? '', available: json['available'] as bool? ?? false, + mobileLink: json['mobileLink'] as String? ?? '', ); } @@ -81,6 +84,7 @@ class MenuItem { 'id': id, 'name': name, 'available': available, + 'mobileLink': mobileLink, }; } } diff --git a/lib/view/dashboard/dashboard_screen.dart b/lib/view/dashboard/dashboard_screen.dart index b8c1402..6ff69e3 100644 --- a/lib/view/dashboard/dashboard_screen.dart +++ b/lib/view/dashboard/dashboard_screen.dart @@ -3,37 +3,24 @@ 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_container.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; -import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/dashbaord/attendance_overview_chart.dart'; import 'package:marco/helpers/widgets/dashbaord/project_progress_chart.dart'; -import 'package:marco/view/layouts/layout.dart'; -import 'package:marco/controller/dynamicMenu/dynamic_menu_controller.dart'; -import 'package:marco/helpers/widgets/dashbaord/dashboard_overview_widgets.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}); - static const String dashboardRoute = "/dashboard"; - static const String employeesRoute = "/dashboard/employees"; - static const String projectsRoute = "/dashboard"; - static const String attendanceRoute = "/dashboard/attendance"; - static const String tasksRoute = "/dashboard/daily-task"; - static const String dailyTasksRoute = "/dashboard/daily-task-Planning"; - static const String dailyTasksProgressRoute = - "/dashboard/daily-task-progress"; - static const String directoryMainPageRoute = "/dashboard/directory-main-page"; - static const String financeMainPageRoute = "/dashboard/finance"; - static const String documentMainPageRoute = "/dashboard/document-main-page"; - static const String serviceprojectsRoute = "/dashboard/service-projects"; - @override State createState() => _DashboardScreenState(); } @@ -42,6 +29,7 @@ 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; @@ -60,11 +48,11 @@ class _DashboardScreenState extends State with UIMixin { Widget build(BuildContext context) { return Layout( child: SingleChildScrollView( - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildDashboardStats(context), + _buildDashboardCards(), MySpacing.height(24), _buildAttendanceChartSection(), MySpacing.height(24), @@ -80,13 +68,9 @@ class _DashboardScreenState extends State with UIMixin { child: DashboardOverviewWidgets.tasksOverview(), ), MySpacing.height(24), - ExpenseByStatusWidget(controller: dashboardController), MySpacing.height(24), - - // Expense Type Report Chart ExpenseTypeReportChart(), - MySpacing.height(24), MonthlyExpenseDashboardChart(), ], @@ -95,7 +79,162 @@ class _DashboardScreenState extends State with UIMixin { ); } - /// Project Progress Chart Section + /// ---------------- 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, + ]; + + 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), + }; + + // 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) { @@ -119,267 +258,45 @@ class _DashboardScreenState extends State with UIMixin { }); } - /// Attendance Chart Section + /// ---------------- Attendance Chart ---------------- Widget _buildAttendanceChartSection() { return Obx(() { - final isAttendanceAllowed = menuController.isMenuAllowed("Attendance"); - - if (!isAttendanceAllowed) { - // 🚫 Don't render anything if attendance menu is not allowed + final attendanceMenu = menuController.menuItems + .firstWhereOrNull((m) => m.id == MenuItems.attendance); + if (attendanceMenu == null || !attendanceMenu.available) return const SizedBox.shrink(); - } - return GetBuilder( - id: 'dashboard_controller', - builder: (projectController) { - 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(), - ), - ), - ), - ); - }, - ); - }); - } - - /// No Project Assigned Message - Widget _buildNoProjectMessage() { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: MyCard( - color: Colors.orange.withOpacity(0.1), - paddingAll: 12, - child: Row( - children: [ - const Icon(Icons.info_outline, color: Colors.orange), - MySpacing.width(8), - Expanded( - child: MyText.bodySmall( - "No projects assigned yet. Please contact your manager to get started.", - color: Colors.orange.shade800, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ), - ); - } - - /// Loading Skeletons - Widget _buildLoadingSkeleton(BuildContext context) { - return Wrap( - spacing: 10, - runSpacing: 10, - children: List.generate( - 4, - (index) => - _buildStatCardSkeleton(MediaQuery.of(context).size.width / 3), - ), - ); - } - - /// Skeleton Card - Widget _buildStatCardSkeleton(double width) { - return MyCard.bordered( - width: width, - height: 100, - paddingAll: 5, - borderRadiusAll: 5, - border: Border.all(color: Colors.grey.withOpacity(0.15)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MyContainer.rounded( - paddingAll: 12, - color: Colors.grey.shade300, - child: const SizedBox(width: 18, height: 18), - ), - MySpacing.height(8), - Container( - height: 12, - width: 60, - color: Colors.grey.shade300, - ), - ], - ), - ); - } - - /// Dashboard Statistics Section - Widget _buildDashboardStats(BuildContext context) { - return Obx(() { - if (menuController.isLoading.value) { - return _buildLoadingSkeleton(context); - } - - if (menuController.hasError.value || menuController.menuItems.isEmpty) { - return Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: MyText.bodySmall( - "Failed to load menus. Please try again later.", - color: Colors.red, - ), - ), - ); - } - - final projectController = Get.find(); final isProjectSelected = projectController.selectedProject != null; - // Keep previous stat items (icons, title, routes) - final stats = [ - _StatItem(LucideIcons.scan_face, "Attendance", contentTheme.success, - DashboardScreen.attendanceRoute), - _StatItem(LucideIcons.users, "Employees", contentTheme.warning, - DashboardScreen.employeesRoute), - _StatItem(LucideIcons.logs, "Daily Task Planning", contentTheme.info, - DashboardScreen.dailyTasksRoute), - _StatItem(LucideIcons.list_todo, "Daily Progress Report", - contentTheme.info, DashboardScreen.dailyTasksProgressRoute), - _StatItem(LucideIcons.folder, "Directory", contentTheme.info, - DashboardScreen.directoryMainPageRoute), - _StatItem(LucideIcons.wallet, "Finance", contentTheme.info, - DashboardScreen.financeMainPageRoute), - _StatItem(LucideIcons.file_text, "Documents", contentTheme.info, - DashboardScreen.documentMainPageRoute), - _StatItem(LucideIcons.briefcase, "Service Projects", contentTheme.info, - DashboardScreen.serviceprojectsRoute), - ]; - - // Safe menu check function to avoid exceptions - bool _isMenuAllowed(String menuTitle) { - try { - return menuController.menuItems.isNotEmpty - ? menuController.isMenuAllowed(menuTitle) - : false; - } catch (e) { - return false; - } - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (!isProjectSelected) _buildNoProjectMessage(), - LayoutBuilder( - builder: (context, constraints) { - int crossAxisCount = (constraints.maxWidth ~/ 80).clamp(2, 8); - double cardWidth = - (constraints.maxWidth - (crossAxisCount - 1) * 6) / - crossAxisCount; - - return Wrap( - spacing: 6, - runSpacing: 6, - alignment: WrapAlignment.start, - children: stats - .where((stat) => - stat.title == "Service Projects" || - _isMenuAllowed(stat.title)) - .map((stat) => - _buildStatCard(stat, isProjectSelected, cardWidth)) - .toList(), - ); - }, - ), - ], - ); - }); - } - - /// Stat Card (Compact + Small) - Widget _buildStatCard( - _StatItem statItem, bool isProjectSelected, double width) { - const double cardHeight = 60; - - final bool isEnabled = statItem.title == "Attendance" || isProjectSelected; - - return Opacity( - opacity: isEnabled ? 1.0 : 0.4, - child: IgnorePointer( - ignoring: !isEnabled, - child: InkWell( - onTap: () => _handleStatCardTap(statItem, isEnabled), - borderRadius: BorderRadius.circular(5), - child: MyCard.bordered( - width: width, - height: cardHeight, - paddingAll: 4, - borderRadiusAll: 5, - border: Border.all(color: Colors.grey.withOpacity(0.15)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildStatCardIconCompact(statItem, size: 12), - MySpacing.height(4), - Flexible( - child: Text( - statItem.title, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 8, - overflow: TextOverflow.visible, - ), - maxLines: 2, - softWrap: true, - ), - ), - ], + return Opacity( + opacity: isProjectSelected ? 1.0 : 0.4, + child: IgnorePointer( + ignoring: !isProjectSelected, + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: SizedBox( + height: 400, + child: AttendanceDashboardChart(), ), ), ), - ), - ); - } - - /// Compact Icon (smaller) - Widget _buildStatCardIconCompact(_StatItem statItem, {double size = 12}) { - return MyContainer.rounded( - paddingAll: 4, - color: statItem.color.withOpacity(0.1), - child: Icon( - statItem.icon, - size: size, - color: statItem.color, - ), - ); - } - - /// Handle Tap - void _handleStatCardTap(_StatItem statItem, bool isEnabled) { - if (!isEnabled) { - Get.defaultDialog( - title: "No Project Selected", - middleText: - "You need to select a project before accessing this section.", - confirm: ElevatedButton( - onPressed: () => Get.back(), - child: const Text("OK"), - ), ); - } else { - Get.toNamed(statItem.route); - } + }); } } -class _StatItem { +/// ---------------- Dashboard Card Models ---------------- +class _DashboardStatItem { final IconData icon; final String title; final Color color; final String route; - _StatItem(this.icon, this.title, this.color, this.route); + _DashboardStatItem(this.icon, this.title, this.color, this.route); +} + +class _DashboardCardMeta { + final IconData icon; + final Color color; + _DashboardCardMeta(this.icon, this.color); } diff --git a/lib/view/finance/finance_screen.dart b/lib/view/finance/finance_screen.dart index c60bc64..eaa1508 100644 --- a/lib/view/finance/finance_screen.dart +++ b/lib/view/finance/finance_screen.dart @@ -11,6 +11,8 @@ 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/controller/dashboard/dashboard_controller.dart'; +import 'package:marco/helpers/utils/permission_constants.dart'; +import 'package:marco/model/dynamicMenu/dynamic_menu_model.dart'; class FinanceScreen extends StatefulWidget { const FinanceScreen({super.key}); @@ -27,6 +29,7 @@ class _FinanceScreenState extends State late Animation _fadeAnimation; final DashboardController dashboardController = Get.put(DashboardController(), permanent: true); + @override void initState() { super.initState(); @@ -117,8 +120,7 @@ class _FinanceScreenState extends State return const Center(child: CircularProgressIndicator()); } - if (menuController.hasError.value || - menuController.menuItems.isEmpty) { + if (menuController.hasError.value || menuController.menuItems.isEmpty) { return const Center( child: Text( "Failed to load menus. Please try again later.", @@ -127,10 +129,18 @@ class _FinanceScreenState extends State ); } - // ✅ Only allow finance cards if "Expense" menu is allowed - final isExpenseAllowed = menuController.isMenuAllowed("Expense & Reimbursement"); + // Filter allowed Finance menus dynamically + final financeMenuIds = [ + MenuItems.expenseReimbursement, + MenuItems.paymentRequests, + MenuItems.advancePaymentStatements, + ]; - if (!isExpenseAllowed) { + final financeMenus = menuController.menuItems + .where((m) => financeMenuIds.contains(m.id) && m.available) + .toList(); + + if (financeMenus.isEmpty) { return const Center( child: Text( "You don’t have access to the Finance section.", @@ -143,7 +153,7 @@ class _FinanceScreenState extends State padding: const EdgeInsets.all(16), child: Column( children: [ - _buildFinanceModulesCompact(), + _buildFinanceModulesCompact(financeMenus), MySpacing.height(24), ExpenseByStatusWidget(controller: dashboardController), MySpacing.height(24), @@ -159,103 +169,115 @@ class _FinanceScreenState extends State } // --- Finance Modules (Compact Dashboard-style) --- - Widget _buildFinanceModulesCompact() { - final stats = [ - _FinanceStatItem(LucideIcons.badge_dollar_sign, "Expense & Reimbursement", - contentTheme.info, "/dashboard/expense-main-page"), - _FinanceStatItem(LucideIcons.receipt_text, "Payment Request", - contentTheme.primary, "/dashboard/payment-request"), - _FinanceStatItem(LucideIcons.wallet, "Advance Payment", - contentTheme.warning, "/dashboard/advance-payment"), - ]; +Widget _buildFinanceModulesCompact(List financeMenus) { + // Map menu IDs to icon + color + final Map financeCardMeta = { + MenuItems.expenseReimbursement: _FinanceCardMeta(LucideIcons.badge_dollar_sign, contentTheme.info), + MenuItems.paymentRequests: _FinanceCardMeta(LucideIcons.receipt_text, contentTheme.primary), + MenuItems.advancePaymentStatements: _FinanceCardMeta(LucideIcons.wallet, contentTheme.warning), + }; - final projectSelected = projectController.selectedProject != null; + // Build the stat items using API-provided mobileLink + final stats = financeMenus.map((menu) { + final meta = financeCardMeta[menu.id]!; - return LayoutBuilder(builder: (context, constraints) { - int crossAxisCount = (constraints.maxWidth ~/ 80).clamp(2, 4); - double cardWidth = - (constraints.maxWidth - (crossAxisCount - 1) * 6) / crossAxisCount; + // --- Log the routing info --- + debugPrint( + "[Finance Card] ID: ${menu.id}, Title: ${menu.name}, Route: ${menu.mobileLink}"); - return Wrap( - spacing: 6, - runSpacing: 6, - alignment: WrapAlignment.end, - children: stats - .map((stat) => - _buildFinanceModuleCard(stat, projectSelected, cardWidth)) - .toList(), - ); - }); - } + return _FinanceStatItem( + meta.icon, + menu.name, + meta.color, + menu.mobileLink, // Each card navigates to its own route + ); + }).toList(); - Widget _buildFinanceModuleCard( - _FinanceStatItem stat, bool isProjectSelected, double width) { - final bool isEnabled = isProjectSelected; + final projectSelected = projectController.selectedProject != null; - return Opacity( - opacity: isEnabled ? 1.0 : 0.4, - child: IgnorePointer( - ignoring: !isEnabled, - child: InkWell( - onTap: () => _onCardTap(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, - ), + return LayoutBuilder(builder: (context, constraints) { + // Determine number of columns dynamically + int crossAxisCount = (constraints.maxWidth ~/ 80).clamp(2, 4); + double cardWidth = (constraints.maxWidth - (crossAxisCount - 1) * 6) / crossAxisCount; + + return Wrap( + spacing: 6, + runSpacing: 6, + alignment: WrapAlignment.end, + children: stats + .map((stat) => _buildFinanceModuleCard(stat, projectSelected, cardWidth)) + .toList(), + ); + }); +} + +Widget _buildFinanceModuleCard( + _FinanceStatItem stat, bool isProjectSelected, double width) { + return Opacity( + opacity: isProjectSelected ? 1.0 : 0.4, // Dim if no project selected + child: IgnorePointer( + ignoring: !isProjectSelected, + child: InkWell( + onTap: () => _onCardTap(stat, isProjectSelected), + 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), ), - MySpacing.height(4), - Flexible( - child: Text( - stat.title, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 10, - overflow: TextOverflow.ellipsis, - ), - maxLines: 2, - softWrap: true, - ), + 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, + softWrap: true, + ), + ), + ], ), ), ), - ); - } + ), + ); +} - void _onCardTap(_FinanceStatItem statItem, bool isEnabled) { - if (!isEnabled) { - Get.defaultDialog( - title: "No Project Selected", - middleText: "Please select a project before accessing this section.", - confirm: ElevatedButton( - onPressed: () => Get.back(), - child: const Text("OK"), - ), - ); - } else { - Get.toNamed(statItem.route); - } +void _onCardTap(_FinanceStatItem statItem, bool isEnabled) { + if (!isEnabled) { + Get.defaultDialog( + title: "No Project Selected", + middleText: "Please select a project before accessing this section.", + confirm: ElevatedButton( + onPressed: () => Get.back(), + child: const Text("OK"), + ), + ); + } else { + // Navigate to the card's specific route + Get.toNamed(statItem.route); } } + } class _FinanceStatItem { final IconData icon; @@ -265,3 +287,9 @@ class _FinanceStatItem { _FinanceStatItem(this.icon, this.title, this.color, this.route); } + +class _FinanceCardMeta { + final IconData icon; + final Color color; + _FinanceCardMeta(this.icon, this.color); +}