From 61acbb019bba2daaf26f186e93033638a3d4d595 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Fri, 12 Sep 2025 12:22:12 +0530 Subject: [PATCH] feat: Refactor DynamicMenuController to remove caching and auto-refresh; update error handling in DashboardScreen and UserProfileBar --- .../dynamicMenu/dynamic_menu_controller.dart | 42 ++----- lib/view/dashboard/dashboard_screen.dart | 113 +++++++++--------- lib/view/layouts/user_profile_right_bar.dart | 14 --- 3 files changed, 63 insertions(+), 106 deletions(-) diff --git a/lib/controller/dynamicMenu/dynamic_menu_controller.dart b/lib/controller/dynamicMenu/dynamic_menu_controller.dart index 4af984c..f55cb1e 100644 --- a/lib/controller/dynamicMenu/dynamic_menu_controller.dart +++ b/lib/controller/dynamicMenu/dynamic_menu_controller.dart @@ -3,7 +3,6 @@ import 'package:get/get.dart'; import 'package:marco/model/dynamicMenu/dynamic_menu_model.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/services/app_logger.dart'; -import 'package:marco/helpers/services/storage/local_storage.dart'; class DynamicMenuController extends GetxController { // UI reactive states @@ -12,30 +11,14 @@ class DynamicMenuController extends GetxController { final RxString errorMessage = ''.obs; final RxList menuItems = [].obs; - Timer? _autoRefreshTimer; - @override void onInit() { super.onInit(); - - // ✅ Load cached menus immediately (so user doesn’t see empty state) - final cachedMenus = LocalStorage.getMenus(); - if (cachedMenus.isNotEmpty) { - menuItems.assignAll(cachedMenus); - logSafe("Loaded ${cachedMenus.length} menus from cache at startup"); - } - - // ✅ Fetch from API in background + // Fetch menus directly from API at startup fetchMenu(); - - // Auto refresh every 15 minutes - _autoRefreshTimer = Timer.periodic( - const Duration(minutes: 15), - (_) => fetchMenu(), - ); } - /// Fetch dynamic menu from API with cache fallback + /// Fetch dynamic menu from API (no local cache) Future fetchMenu() async { isLoading.value = true; hasError.value = false; @@ -44,13 +27,9 @@ class DynamicMenuController extends GetxController { try { final responseData = await ApiService.getMenuApi(); if (responseData != null) { - // Parse JSON into MenuResponse final menuResponse = MenuResponse.fromJson(responseData); menuItems.assignAll(menuResponse.data); - // Save for offline use - await LocalStorage.setMenus(menuItems); - logSafe("✅ Menu loaded from API with ${menuItems.length} items"); } else { _handleApiFailure("Menu API returned null response"); @@ -65,26 +44,19 @@ class DynamicMenuController extends GetxController { void _handleApiFailure(String logMessage) { logSafe(logMessage, level: LogLevel.error); - final cachedMenus = LocalStorage.getMenus(); - if (cachedMenus.isNotEmpty) { - menuItems.assignAll(cachedMenus); - errorMessage.value = "⚠️ Using offline menus (latest sync failed)"; - logSafe("Loaded ${menuItems.length} menus from cache after failure"); - } else { - hasError.value = true; - errorMessage.value = "❌ Unable to load menus. Please try again later."; - menuItems.clear(); - } + // No cache available, show error state + hasError.value = true; + errorMessage.value = "❌ Unable to load menus. Please try again later."; + menuItems.clear(); } bool isMenuAllowed(String menuName) { final menu = menuItems.firstWhereOrNull((m) => m.name == menuName); - return menu?.available ?? false; // default false if not found + return menu?.available ?? false; } @override void onClose() { - _autoRefreshTimer?.cancel(); super.onClose(); } } diff --git a/lib/view/dashboard/dashboard_screen.dart b/lib/view/dashboard/dashboard_screen.dart index a169aad..a2269e3 100644 --- a/lib/view/dashboard/dashboard_screen.dart +++ b/lib/view/dashboard/dashboard_screen.dart @@ -40,8 +40,7 @@ class DashboardScreen extends StatefulWidget { class _DashboardScreenState extends State with UIMixin { final DashboardController dashboardController = Get.put(DashboardController(), permanent: true); - final DynamicMenuController menuController = - Get.put(DynamicMenuController(), permanent: true); + final DynamicMenuController menuController = Get.put(DynamicMenuController()); bool hasMpin = true; @@ -243,7 +242,8 @@ class _DashboardScreenState extends State with UIMixin { if (menuController.isLoading.value) { return _buildLoadingSkeleton(context); } - if (menuController.hasError.value) { + if (menuController.hasError.value && menuController.menuItems.isEmpty) { + // ❌ Only show error if there are no menus at all return Padding( padding: const EdgeInsets.all(16), child: Center( @@ -299,70 +299,70 @@ class _DashboardScreenState extends State with UIMixin { } /// Stat Card (Compact with wrapping text) -/// Stat Card (Compact with wrapping text) -Widget _buildStatCard(_StatItem statItem, bool isProjectSelected) { - const double cardWidth = 80; - const double cardHeight = 70; + /// Stat Card (Compact with wrapping text) + Widget _buildStatCard(_StatItem statItem, bool isProjectSelected) { + const double cardWidth = 80; + const double cardHeight = 70; - // ✅ Attendance should always be enabled - final bool isEnabled = statItem.title == "Attendance" || isProjectSelected; + // ✅ Attendance should always be enabled + 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(8), - child: MyCard.bordered( - width: cardWidth, - height: cardHeight, - paddingAll: 4, - borderRadiusAll: 8, - border: Border.all(color: Colors.grey.withOpacity(0.15)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildStatCardIconCompact(statItem), - MySpacing.height(4), - Expanded( - child: Center( - child: Text( - statItem.title, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 10, - overflow: TextOverflow.visible, + return Opacity( + opacity: isEnabled ? 1.0 : 0.4, + child: IgnorePointer( + ignoring: !isEnabled, + child: InkWell( + onTap: () => _handleStatCardTap(statItem, isEnabled), + borderRadius: BorderRadius.circular(8), + child: MyCard.bordered( + width: cardWidth, + height: cardHeight, + paddingAll: 4, + borderRadiusAll: 8, + border: Border.all(color: Colors.grey.withOpacity(0.15)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildStatCardIconCompact(statItem), + MySpacing.height(4), + Expanded( + child: Center( + child: Text( + statItem.title, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 10, + overflow: TextOverflow.visible, + ), + maxLines: 2, + softWrap: true, ), - maxLines: 2, - softWrap: true, ), ), - ), - ], + ], + ), ), ), ), - ), - ); -} - -/// 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); } -} + /// 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); + } + } /// Compact Icon Widget _buildStatCardIconCompact(_StatItem statItem) { @@ -378,7 +378,6 @@ void _handleStatCardTap(_StatItem statItem, bool isEnabled) { } /// Handle Tap - } class _StatItem { diff --git a/lib/view/layouts/user_profile_right_bar.dart b/lib/view/layouts/user_profile_right_bar.dart index 45dc62f..d3d86d8 100644 --- a/lib/view/layouts/user_profile_right_bar.dart +++ b/lib/view/layouts/user_profile_right_bar.dart @@ -10,7 +10,6 @@ import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/model/employees/employee_info.dart'; import 'package:marco/controller/auth/mpin_controller.dart'; import 'package:marco/view/employees/employee_profile_screen.dart'; -import 'package:marco/view/document/user_document_screen.dart'; class UserProfileBar extends StatefulWidget { final bool isCondensed; @@ -178,12 +177,6 @@ class _UserProfileBarState extends State onTap: _onProfileTap, ), SizedBox(height: spacingHeight), - _menuItemRow( - icon: LucideIcons.file_text, - label: 'My Documents', - onTap: _onDocumentsTap, - ), - SizedBox(height: spacingHeight), _menuItemRow( icon: LucideIcons.settings, label: 'Settings', @@ -250,13 +243,6 @@ class _UserProfileBarState extends State )); } - void _onDocumentsTap() { - Get.to(() => UserDocumentsPage( - entityId: "${employeeInfo.id}", - isEmployee: true, - )); - } - void _onMpinTap() { final controller = Get.put(MPINController()); if (hasMpin) controller.setChangeMpinMode();