import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../core/routing/routes.dart'; import '../../core/services/api_service.dart'; import '../../data/services/auth_service.dart'; import '../providers/manager_provider.dart'; import '../providers/userRoleProvider.dart'; import '../screens/UserManagement/Profile/profile_web.dart'; import '../screens/staff/Enquiry/Proposal_QuickCreation/Proposal_QuickCreation.dart'; import '../themes/indicators/side_Drawer_Panel.dart'; class AppHeader extends ConsumerStatefulWidget implements PreferredSizeWidget { const AppHeader({super.key}); @override ConsumerState createState() => _AppHeaderState(); @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } class _AppHeaderState extends ConsumerState { String? role; ApiService apiService = ApiService(); Map? profileData; String? _token; OverlayEntry? _overlayEntry; String? _activeMenu; Timer? _queueTimer; int totalAssigned = 0; int totalPending = 0; int todayAssigned = 0; int todayPending = 0; int todayInProgress = 0; int todayCompleted = 0; int queueCount = 0; // badge count @override void initState() { super.initState(); _loadUser(); _initializeToken(); _setActiveMenu(); } @override void dispose() { _queueTimer?.cancel(); _hidePopup(); super.dispose(); } // Set active menu based on current route void _setActiveMenu() { WidgetsBinding.instance.addPostFrameCallback((_) { final currentRoute = GoRouterState.of(context).uri.path.toLowerCase(); setState(() { _activeMenu = _menuKeyForRoute(currentRoute); }); }); } String? _menuKeyForRoute(String currentRoute) { final route = currentRoute.toLowerCase(); if (route.contains('dashboard') || route.contains('partnerportaldashboard')) { return 'Dashboard'; } if (route.contains('enquiry')) { return 'Enquiry'; } if (route.contains('endorsement')) { return 'Endorsement'; } if (route.contains('agent') || route.contains('staff') || route.contains('salesexecutive') || route.contains('pos')) { return 'User'; } if (route.contains('claim') || route.contains('policy') || route.contains('attendance') || route.contains('monthlycommission') || route.contains('gridlist') || route.contains('gridview')) { return 'Reports'; } if (route.contains('broker') || route.contains('payment') || route.contains('vehicletype') || route.contains('endorsementtype') || route.contains('insurer')) { return 'Masters'; } if (route.contains('payoutgrid')) { return 'PayoutGrid'; } if (route.contains('payoutupload')) { return 'PayoutUpload'; } if (route.contains('payoutreport')) { return 'PayoutReport'; } if (route.contains('invoice') || (route.contains('payout') && !route.contains('payoutgrid'))) { return 'Payout'; } return null; } Future _initializeToken() async { _token = await AuthService.getToken(); if (_token != null) { final Map decodedToken = Jwt.parseJwt(_token!); setState(() { profileData = decodedToken['data']; }); } } Future _loadUser() async { final prefs = await SharedPreferences.getInstance(); setState(() { role = prefs.getString('userRole') ?? "Guest"; }); print('AppHeader Role - $role'); // Initial fetch + auto refresh WidgetsBinding.instance.addPostFrameCallback((_) { print('111'); if (role == 'staff') { print('staff 111'); getStaffLevelCount(profileData?['manager_id'], profileData?['id']); _queueTimer = Timer.periodic( const Duration(seconds: 30), (_) => getStaffLevelCount( profileData?['manager_id'], profileData?['id'], ), ); } }); } Future getStaffLevelCount(managerId, userId) async { print('staff 222'); print('$managerId ,$userId'); print('staff 333'); try { final response = await apiService.fetchStaffLevelCount(managerId, userId); if (response['status'] == 'success') { final List summary = response['data']['staff_level_pending_summary'] ?? []; int _totalAssigned = 0; int _totalPending = 0; int _todayAssigned = 0; int _todayPending = 0; int _todayInProgress = 0; int _todayCompleted = 0; for (final item in summary) { _totalAssigned += int.parse(item['total_assigned'] ?? '0'); _totalPending += int.parse(item['total_pending'] ?? '0'); _todayAssigned += int.parse(item['today_assigned'] ?? '0'); _todayPending += int.parse(item['today_pending'] ?? '0'); _todayInProgress += int.parse(item['today_in_progress'] ?? '0'); _todayCompleted += int.parse(item['today_completed'] ?? '0'); } if (!mounted) return; setState(() { totalAssigned = _totalAssigned; totalPending = _totalPending; todayAssigned = _todayAssigned; todayPending = _todayPending; todayInProgress = _todayInProgress; todayCompleted = _todayCompleted; queueCount = totalPending + todayPending + todayInProgress; }); } } catch (e) { debugPrint('Queue API Exception: $e'); } } // Clear dashboard filters Future _clearDashboardFilters() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('dashboardKeyProvider'); await prefs.remove('dashboardStatusProvider'); await prefs.remove('dashboardStaffIdProvider'); } // Show hover popup void _showPopup(BuildContext context, Offset offset, Size size, String key) { _hidePopup(); _overlayEntry = OverlayEntry( builder: (_) { return Positioned( left: offset.dx, // top: offset.dy + size.height + 8, // Reduced +8 to +2 to create an overlap so the mouse never leaves // a "hoverable" area, which prevents the exit flicker. top: offset.dy + size.height + 2, child: MouseRegion( onExit: (_) => _hidePopup(), child: Material( elevation: 8, shadowColor: Colors.black26, borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: Colors.white, border: Border.all(color: Colors.grey.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (key == 'User') ...[ _buildPopupItem( label: "Partner", onTap: () { _hidePopup(); setState(() => _activeMenu = 'User'); context.go(AppRoutes.agentLst); }, ), const SizedBox(height: 2), _buildPopupItem( label: "Staff", onTap: () { _hidePopup(); setState(() => _activeMenu = 'User'); context.go(AppRoutes.staffLst); }, ), const SizedBox(height: 2), _buildPopupItem( label: "Sales Executive", onTap: () { _hidePopup(); setState(() => _activeMenu = 'User'); context.go(AppRoutes.salesExecutiveLst); }, ), const SizedBox(height: 2), _buildPopupItem( label: "POS", onTap: () { _hidePopup(); setState(() => _activeMenu = 'User'); context.go(AppRoutes.posLst); }, ), ], if (key == 'Reports') ...[ if (role == 'manager' || role == 'Accounts' || role == 'agent') ...[ _buildPopupItem( label: "Payout List", onTap: () { _hidePopup(); setState(() => _activeMenu = 'Reports'); context.go( role == 'agent' ? AppRoutes.gridView : AppRoutes.gridList, ); }, ), const SizedBox(height: 2), ], if (role == 'manager') ...[ _buildPopupItem( label: "Attendance", onTap: () { _hidePopup(); setState(() => _activeMenu = 'Reports'); context.go(AppRoutes.allStaffAttendance); }, ), ], SizedBox(height: 2), _buildPopupItem( label: "Claims", onTap: () { _hidePopup(); setState(() => _activeMenu = 'Reports'); context.go(AppRoutes.claimlist); }, ), const SizedBox(height: 2), _buildPopupItem( label: "Policy", onTap: () { setState(() => _activeMenu = 'Reports'); _hidePopup(); context.go(AppRoutes.policylist); }, ), ], if (key == 'Masters') ...[ if (role == 'manager') ...[ _buildPopupItem( label: "Broker", onTap: () { _hidePopup(); setState(() => _activeMenu = 'Masters'); context.go(AppRoutes.brokerLst); }, ), ], SizedBox(height: 2), _buildPopupItem( label: "Endorsement Type", onTap: () { setState(() => _activeMenu = 'Masters'); _hidePopup(); context.go(AppRoutes.endorsementTypeLst); }, ), _buildPopupItem( label: "Vehicle Type", onTap: () { setState(() => _activeMenu = 'Masters'); _hidePopup(); context.go(AppRoutes.vehicleTypeLst); }, ), SizedBox(height: 2), _buildPopupItem( label: "Payment Mode", onTap: () { setState(() => _activeMenu = 'Masters'); _hidePopup(); context.go(AppRoutes.paymentModeLst); }, ), SizedBox(height: 2), _buildPopupItem( label: "Insurer", onTap: () { setState(() => _activeMenu = 'Masters'); _hidePopup(); context.go(AppRoutes.insurerLst); }, ), ], if (key == 'Payout' && role == 'Accounts') ...[ _buildPopupItem( label: "Payout", onTap: () { _hidePopup(); setState(() => _activeMenu = 'Payout'); context.go(AppRoutes.payoutList); }, ), const SizedBox(height: 2), _buildPopupItem( label: "Payout Upload", onTap: () { _hidePopup(); setState(() => _activeMenu = 'PayoutUpload'); context.go(AppRoutes.payoutUpload); }, ), const SizedBox(height: 2), _buildPopupItem( label: "Payout Reports", onTap: () { _hidePopup(); setState(() => _activeMenu = 'PayoutReport'); context.go(AppRoutes.payoutReport); }, ), ], ], ), ), ), ), ); }, ); Overlay.of(context, rootOverlay: true).insert(_overlayEntry!); } void _hidePopup() { if (_overlayEntry != null) { _overlayEntry?.remove(); _overlayEntry = null; // Optional: add a small setState if needed to refresh the UI layer if (mounted) setState(() {}); } } @override Widget build(BuildContext context) { final roleId = ref.watch(userRoleProvider); final currentRouteLower = GoRouterState.of(context).uri.path.toLowerCase(); var activeMenu = _menuKeyForRoute(currentRouteLower) ?? _activeMenu; if (currentRouteLower.contains('payoutgrid') && roleId != 'agent') { activeMenu = 'Reports'; } return AppBar( // backgroundColor: const Color(0xFFD6F6F4), backgroundColor: const Color(0xFFE3F9F8), elevation: 0, automaticallyImplyLeading: false, titleSpacing: 24, toolbarHeight: 64, title: Row( children: [ // Logo Image.asset( "assets/login/nhance-partner-logo.png", height: 45, width: 95, ), Spacer(), // Dashboard if (roleId != 'staff' && role != 'Accounts') ...[ _buildMenuItem( icon: Icons.dashboard, label: "Dashboard", isActive: activeMenu == 'Dashboard', onTap: () async { _hidePopup(); setState(() => _activeMenu = 'Dashboard'); await _clearDashboardFilters(); final dashboardRoute = roleId == 'agent' ? AppRoutes.partnerPortalDashboard : AppRoutes.dashboard; context.go(dashboardRoute); }, ), const SizedBox(width: 15), ], SizedBox(width: 8), // Enquiry if (role != 'Accounts') ...[ _buildMenuItem( icon: Icons.list_alt_rounded, label: "Enquiry", isActive: activeMenu == 'Enquiry', onTap: () async { _hidePopup(); setState(() => _activeMenu = 'Enquiry'); await _clearDashboardFilters(); if (roleId == 'agent') { context.go(AppRoutes.enquiryLst); } else { context.go(AppRoutes.enquiryForStaff); } }, ), ], const SizedBox(width: 8), _buildMenuItem( isActive: activeMenu == 'Endorsement', icon: Icons.checklist_outlined, label: "Endorsement", onTap: () async { _hidePopup(); setState(() => _activeMenu = 'Endorsement'); await _clearDashboardFilters(); context.go(AppRoutes.Endorsement); }, ), const SizedBox(width: 8), // User (Manager Only) - with hover popup if (roleId == 'manager' || role == 'Accounts') ...[ _buildHoverMenuItem( icon: Icons.person_add_alt, label: "User", popupKey: 'User', isActive: activeMenu == 'User', ), const SizedBox(width: 8), ], // User (Manager Only) - with hover popup if (roleId == 'manager') ...[ _buildHoverMenuItem( icon: Icons.settings_suggest_outlined, label: "Masters", popupKey: 'Masters', isActive: activeMenu == 'Masters', ), const SizedBox(width: 8), ], // Reports (Not Staff) - with hover popup if (roleId != 'staff') ...[ _buildHoverMenuItem( icon: Icons.receipt_long, label: "Reports", popupKey: 'Reports', isActive: activeMenu == 'Reports', ), const SizedBox(width: 8), ], // Payout (Accounts only) — one top-level menu with submenu if (role == 'Accounts') ...[ _buildHoverMenuItem( icon: Icons.payments_outlined, label: "Payout", popupKey: 'Payout', isActive: activeMenu == 'Payout' || activeMenu == 'PayoutUpload' || activeMenu == 'PayoutReport', ), const SizedBox(width: 8), ], // Partner (agent): Payout Report — same route as Accounts if (role == 'agent') ...[ const SizedBox(width: 8), _buildMenuItem( icon: Icons.table_chart_outlined, label: "Payout Report", isActive: activeMenu == 'PayoutReport', onTap: () { _hidePopup(); setState(() => _activeMenu = 'PayoutReport'); context.go(AppRoutes.payoutReport); }, ), ], const Spacer(), // Raise Enquiry button for agents if (role == 'agent') ...[ Container( height: 36, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: const Color(0xFF425B5B), borderRadius: BorderRadius.circular(6), ), child: InkWell( onTap: () async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('enqAgentDataId'); ref.read(enquiryIdProvider.notifier).state = null; context.go(AppRoutes.tabEnquiry); }, child: Center( child: Text( 'Raise Enquiry', style: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 13, ), ), ), ), ), const SizedBox(width: 16), ], if (role == 'staff') ...[ _buildQueueIcon(), const SizedBox(width: 12), ], // if (role != 'Accounts') ...[ if (!['Accounts', 'agent'].contains(role)) ...[ // Proposal button Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade200, width: 1), ), child: IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.quora, color: Colors.black87, size: 20), onPressed: () { SideDrawerPanel.show( context: context, title: 'Quote Creation', child: CreateProposal_Quick(), ); }, tooltip: 'Quick Quote', ), ), const SizedBox(width: 12), ], // Notifications button // Container( // width: 36, // height: 36, // decoration: BoxDecoration( // color: Colors.white, // borderRadius: BorderRadius.circular(8), // border: Border.all(color: Colors.grey.shade200, width: 1), // ), // child: IconButton( // padding: EdgeInsets.zero, // icon: const Icon( // Icons.notifications_none, // color: Colors.black87, // size: 20, // ), // onPressed: () { // debugPrint("Notifications tapped"); // }, // tooltip: 'Notifications', // ), // ), // // const SizedBox(width: 12), // Profile with popup PopupMenuButton( color: Colors.white, offset: const Offset(0, 52), child: Container( height: 36, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), // border: Border.all(color: Colors.grey.shade200, width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( radius: 14, // backgroundColor: const Color(0xFF4a90e2), backgroundColor: const Color(0xFF2E7D6E), child: Text( profileData?['name']?.substring(0, 2).toUpperCase() ?? 'A', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10, ), ), ), const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( profileData?['name'] ?? 'Manager', style: GoogleFonts.inter( fontWeight: FontWeight.w600, fontSize: 11, color: Colors.black87, ), ), Text( (role == 'agent') ? 'Partner' : role ?? 'Manager', style: GoogleFonts.inter( fontSize: 9, color: Colors.grey.shade600, ), ), ], ), const SizedBox(width: 4), Icon( Icons.keyboard_arrow_down, size: 16, color: Colors.grey.shade700, ), ], ), ), itemBuilder: (context) => [ PopupMenuItem( enabled: false, child: Container( width: 480, color: Colors.white, child: ProfilePopUp(), ), ), ], ), const SizedBox(width: 12), // Logout button Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade200, width: 1), ), child: IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.logout, color: Colors.black87, size: 18), onPressed: () async { debugPrint("Logout tapped"); AuthService.clearToken(); if (role != 'handler') { await apiService.logoutUsingAPI(context); } context.go(AppRoutes.login); }, tooltip: 'Logout', ), ), ], ), ); } Widget _buildQueueIcon() { return Builder( builder: (iconContext) { return MouseRegion( onEnter: (_) { final box = iconContext.findRenderObject() as RenderBox; _showQueuePopup(context, box.localToGlobal(Offset.zero), box.size); }, onExit: (_) => _hidePopup(), child: Stack( clipBehavior: Clip.none, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade200), ), child: const Icon(Icons.priority_high, size: 20), ), // 🔴 Badge // if (queueCount > 0) Positioned( right: -6, top: -6, child: Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), constraints: const BoxConstraints( minWidth: 18, minHeight: 18, ), child: Text( queueCount.toString(), textAlign: TextAlign.center, style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), ], ), ); }, ); } void _showQueuePopup(BuildContext context, Offset offset, Size size) { _hidePopup(); _overlayEntry = OverlayEntry( builder: (_) => Positioned( left: offset.dx, top: offset.dy + size.height + 4, child: MouseRegion( onExit: (_) => _hidePopup(), child: Material( elevation: 10, borderRadius: BorderRadius.circular(8), child: Container( width: 180, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _queueRow('Total Assigned', totalAssigned), _queueRow('Total Pending', totalPending), const Divider(), _queueRow('Today Assigned', todayAssigned), _queueRow('Today Pending', todayPending), _queueRow('Today In Progress', todayInProgress), _queueRow('Today Completed', todayCompleted), ], ), ), ), ), ), ); Overlay.of(context, rootOverlay: true).insert(_overlayEntry!); } Widget _queueRow(String label, int value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: GoogleFonts.inter(fontSize: 12, color: Colors.grey.shade700)), Text(value.toString(), style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w600)), ], ), ); } // Simple menu item widget Widget _buildMenuItem({ required IconData icon, required String label, required VoidCallback onTap, bool isActive = false, }) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(6), child: MouseRegion( onEnter: (_) => _hidePopup(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: isActive ? const Color(0xFF2E7D6E).withOpacity(0.03) : Colors.transparent, borderRadius: BorderRadius.circular(6), border: isActive ? Border( bottom: BorderSide( color: const Color(0xFF2E7D6E), width: 2, ), ) : null, ), // decoration: BoxDecoration(borderRadius: BorderRadius.circular(6)), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 18, color: isActive ? const Color(0xFF2E7D6E) : Colors.black87, ), const SizedBox(width: 6), Text( label, style: GoogleFonts.inter( color: isActive ? const Color(0xFF2E7D6E) : Colors.black87, fontSize: 13, fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, ), ), ], ), ), ), ); } // Menu item with hover popup Widget _buildHoverMenuItem({ required IconData icon, required String label, required String popupKey, bool isActive = false, }) { return Builder( builder: (itemContext) { return MouseRegion( onEnter: (_) { final renderBox = itemContext.findRenderObject() as RenderBox; final offset = renderBox.localToGlobal(Offset.zero); final size = renderBox.size; _showPopup(context, offset, size, popupKey); }, onExit: (event) { // We check if the mouse is moving downwards (toward the popup) // If not moving toward popup, hide it. if (event.localPosition.dy < 0 || event.localPosition.dx < 0 || event.localPosition.dx > 100) { _hidePopup(); } }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: isActive ? const Color(0xFF2E7D6E).withOpacity(0.02) : Colors.transparent, borderRadius: BorderRadius.circular(6), border: isActive ? Border( bottom: BorderSide( color: const Color(0xFF2E7D6E), width: 2, ), ) : null, ), // decoration: BoxDecoration(borderRadius: BorderRadius.circular(6)), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 18, color: isActive ? const Color(0xFF2E7D6E) : Colors.black87, ), const SizedBox(width: 6), Text( label, style: GoogleFonts.inter( color: isActive ? const Color(0xFF2E7D6E) : Colors.black87, fontSize: 13, fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, ), ), const SizedBox(width: 2), Icon( Icons.arrow_drop_down, color: isActive ? const Color(0xFF2E7D6E) : Colors.black87, size: 18, ), ], ), ), ); }, ); } // Popup item widget Widget _buildPopupItem({required String label, required VoidCallback onTap}) { return _HoverableContainer( label: label, onTap: () { _hidePopup(); // Force hide before executing the specific onTap logic onTap(); }, normalColor: Colors.white, hoverColor: const Color(0xFFF5F5F5), ); } @override Size get preferredSize => const Size.fromHeight(64); } // Hoverable container for popup items class _HoverableContainer extends StatefulWidget { final String label; final VoidCallback onTap; final Color normalColor; final Color hoverColor; const _HoverableContainer({ required this.label, required this.onTap, required this.normalColor, required this.hoverColor, }); @override _HoverableContainerState createState() => _HoverableContainerState(); } class _HoverableContainerState extends State<_HoverableContainer> { bool _isHovered = false; @override Widget build(BuildContext context) { return MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), child: InkWell( onTap: widget.onTap, child: Container( width: 140, decoration: BoxDecoration( color: _isHovered ? widget.hoverColor : widget.normalColor, borderRadius: BorderRadius.circular(6), ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( widget.label, style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w500, color: Colors.black87, ), ), // Icon( // Icons.arrow_forward_ios_rounded, // size: 10, // color: Colors.grey.shade600, // ), ], ), ), ), ); } }