import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:frontend/config/apiUrl.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; // don't forget import '../services/apiService.dart'; import '../utils/auth_utils.dart'; enum TabSelection { dashboard, allTrips, myTrips, myApprovals, allMenu } class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { final bool isDesktop; final String? logoUrl; final String? username; final List? actions; final bool showBackButton; const CustomAppBar({ super.key, this.logoUrl, this.username, this.actions, this.showBackButton = false, required this.isDesktop, }); @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); @override _CustomAppBarState createState() => _CustomAppBarState(); } class _CustomAppBarState extends State { final ApiService apiService = ApiService(); late TabSelection selectedTab; String? token; Map? userData; Map? fetchedUserData; Map userDetails = {}; Map profileUserDetails = {}; Map? selectedOrg; // Color? layoutColor; Color? layoutColor = Colors.white10; Color? bodyColor; void initState() { super.initState(); // initializeData(); WidgetsBinding.instance.addPostFrameCallback((_) { initializeData(); getOrganizationData(); }); if (userData?["role"] == "Org Admin" || userData?["role"] == "Travel Admin") { selectedTab = TabSelection.dashboard; // selectedTab = TabSelection.allTrips; } else { selectedTab = TabSelection.myTrips; } } Future getProfileUser() async { print("getProfileUser - $userData"); print("getProfileUser - ${userData?['user_id']}"); final userIdRaw = userData?['user_id']; final int? userId = userIdRaw is int ? userIdRaw : int.tryParse(userIdRaw.toString()); if (userId != null) { profileUserDetails = await apiService.getSingleUser(userId); print("getProfileUser-$profileUserDetails"); } else { print("❌ Invalid user_id: $userIdRaw"); } print("getProfileUser-$profileUserDetails"); } Future initializeData() async { print("initializeDatainitializeData"); token = await getToken(); fetchedUserData = await getUserData(); if (token == null || fetchedUserData == null) { print("Token or USerId missing"); return; } setState(() { userData = fetchedUserData; }); getProfileUser(); } Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString("auth_token"); } Future?> getUserData() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); if (userDataString != null) { try { userDetails = jsonDecode(userDataString); return { "user_id": userDetails["user_id"].toString(), "name": "${userDetails["first_name"]} ${userDetails["last_name"]}", "email": userDetails["email"] ?? "", "role": userDetails["role"] ?? "", "last_login_at": userDetails["last_login_at"] ?? "", }; } catch (e) { print("Error decoding user data: $e"); return null; } } return null; } Future getOrganizationData() async { try { print("getUpdatedServices"); final prefs = await SharedPreferences.getInstance(); final String? orgDataString = prefs.getString('org_data'); if (orgDataString != null) { // final result = await apiService.fetchOrganization(); final Map result = jsonDecode(orgDataString); print("UUPdatedServices - $result"); final prefs = await SharedPreferences.getInstance(); print("UUPdatedServices - $result"); setState(() { selectedOrg = result; layoutColor = selectedOrg?['layout_color'] != null ? Color(int.parse(selectedOrg!['layout_color'])) : Colors.black; bodyColor = selectedOrg?['color'] != null ? Color( int.parse( selectedOrg!['color'].toString().replaceFirst('0x', ''), radix: 16, ), ) : Colors.blue; String? rawLogoPath = selectedOrg?['logo']; if (rawLogoPath != null && rawLogoPath.contains('/assets')) { const baseUrl = apiUrl; // const baseUrl = "https://apitest.tripapprovaltool.com"; final assetPath = rawLogoPath.split('/assets').last; selectedOrg!['logo'] = "$baseUrl/assets$assetPath"; } }); // Save to SharedPreferences await prefs.setString('layout_color', selectedOrg?['layout_color']); await prefs.setString('secondary_color', selectedOrg?['secondary_color']); await prefs.setString('ternary_color', selectedOrg?['ternary_color']); await prefs.setString('body_color', selectedOrg?['color']); await prefs.setString('body_color', selectedOrg?['plan_action']); print( "Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor", ); } } catch (e) { print("Error : $e"); } } Future getOrganizationData1() async { try { print("getUpdatedServices"); final result = await apiService.fetchOrganization(); final prefs = await SharedPreferences.getInstance(); print("UUPdatedServices - $result"); setState(() { selectedOrg = result; layoutColor = selectedOrg?['layout_color'] != null ? Color(int.parse(selectedOrg!['layout_color'])) : Colors.white; bodyColor = selectedOrg?['color'] != null ? Color( int.parse( selectedOrg!['color'].toString().replaceFirst('0x', ''), radix: 16, ), ) : Colors.blue; String? rawLogoPath = selectedOrg?['logo']; if (rawLogoPath != null && rawLogoPath.contains('/assets')) { const baseUrl = "https://apitest.tripapprovaltool.com"; final assetPath = rawLogoPath.split('/assets').last; selectedOrg!['logo'] = "$baseUrl/assets$assetPath"; } }); // Save to SharedPreferences await prefs.setString('layout_color', selectedOrg?['layout_color']); await prefs.setString('secondary_color', selectedOrg?['secondary_color']); await prefs.setString('ternary_color', selectedOrg?['ternary_color']); await prefs.setString('body_color', selectedOrg?['color']); await prefs.setString('body_color', selectedOrg?['plan_action']); print( "Layout Color- ${selectedOrg?['layout_color']} - $layoutColor ---------- bodyColor - $bodyColor", ); } catch (e) { print("Error : $e"); } } // void handleTabChange(TabSelection tab, String route) { // final currentUri = // GoRouterState.of(context).uri.toString(); // ✅ safer than `.location` // print("currentUri - $currentUri"); // // if (currentUri != route) { // setState(() { // selectedTab = tab; // }); // context.go(route); // } // } void handleTabChange(TabSelection tab, String route) { final currentUri = GoRouterState.of(context).uri.toString(); if (currentUri != route) { context.go(route); // 🔄 Let navigation happen // The tab selection will automatically be updated by didChangeDependencies } } @override void didChangeDependencies() { super.didChangeDependencies(); final location = GoRouterState.of(context).uri.toString(); print("location - $location"); setState(() { if (location.contains('/StatusDashboard')) { selectedTab = TabSelection.dashboard; } else if (location.contains('/listAllPlan') || location.contains('/allTrips/trips')) { selectedTab = TabSelection.allTrips; } else if (location.contains('/listPlan') || location.contains('/createPlan') || location.contains('/listTravelAgentPlan')) { selectedTab = TabSelection.myTrips; } else if (location.contains('/ApprovalList') || location.contains('/approvallist') || location.contains('/approver/plans')) { selectedTab = TabSelection.myApprovals; } else { selectedTab = TabSelection.allMenu; } }); } Future logout(BuildContext context) async { // Clear localStorage final prefs = await SharedPreferences.getInstance(); await prefs.clear(); // Clears all keys // Optional: clear sessionStorage if used // html.window.sessionStorage.clear(); // Navigate to login or home page context.go('/'); } void _handleBackButton() { if (selectedTab == TabSelection.dashboard) { // Do nothing or show "Press again to exit" toast print("Blocked back on dashboard"); } else { setState(() { selectedTab = TabSelection.dashboard; }); } } @override Widget build(BuildContext context) { return PopScope( canPop: false, // Allow back navigation only if not login screen onPopInvokedWithResult: (didPop, result) { if (didPop) return; _handleBackButton(); // Show exit confirmation dialog }, child: AppBar( backgroundColor: Colors.white, surfaceTintColor: Colors.white, // elevation: 3, automaticallyImplyLeading: !widget.isDesktop, iconTheme: IconThemeData( color: layoutColor, // 👈 Set your desired icon color here ), titleSpacing: 0, title: !widget.isDesktop ? Text('') : Padding( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.05, ), child: Row( children: [ Padding( padding: const EdgeInsets.all(10), // padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10), child: selectedOrg?['logo'] != null ? SizedBox( height: 50, child: ClipRect( child: Image.network( selectedOrg!['logo'], width: 130, //130 height: 80, //80 fit: BoxFit.contain, errorBuilder: ( context, error, stackTrace, ) { return const CircleAvatar( radius: 20, child: Icon( Icons.add_a_photo, size: 10, color: Colors.grey, ), // backgroundColor: Colors.redAccent, // child: Icon(Icons.error, size: 10), ); }, ), ), ) : const CircleAvatar( radius: 20, // backgroundColor: Colors.white, child: Icon( Icons.add_a_photo, size: 10, color: Colors.grey, ), ), ), SizedBox(width: MediaQuery.of(context).size.width * 0.18), // Spacer(), Container( width: MediaQuery.of(context).size.width * 0.35, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (userData?["role"] == "Org Admin" || userData?["role"] == "Travel Admin") buildNavItem( "Dashboard", () => handleTabChange( TabSelection.dashboard, '/StatusDashboard', ), layoutColor!, isSelected: selectedTab == TabSelection.dashboard, icon: Icons.dashboard, // icon: Icons.insights_outlined, ), if (userData?["role"] == "Org Admin" || userData?["role"] == "Travel Admin") const SizedBox(width: 20), if (userData?["role"] == "Org Admin" || userData?["role"] == "Travel Admin") buildNavItem( "All Trips", () => handleTabChange( TabSelection.allTrips, '/listAllPlan', ), layoutColor!, isSelected: selectedTab == TabSelection.allTrips, icon: Icons.format_list_bulleted_rounded, // icon: Icons.insights_outlined, ), if (userDetails["role"] != "Travel Agent") const SizedBox(width: 20), if (userData?["role"] == "Travel Agent") buildNavItem( "Trips", () => handleTabChange( TabSelection.myTrips, '/listTravelAgentPlan', ), layoutColor!, // () => context.go('/listTravelAgentPlan'), isSelected: selectedTab == TabSelection.myTrips, icon: Icons.shopping_bag_outlined, // icon: Icons.request_page_outlined, ), if (userData?["role"] != "Travel Agent") // for others buildNavItem( "My Trips", () => handleTabChange( TabSelection.myTrips, '/listPlan', ), layoutColor!, // () => context.go('/listPlan'), isSelected: selectedTab == TabSelection.myTrips, icon: Icons.shopping_bag_outlined, ), const SizedBox(width: 20), if (userData?["role"] != "Travel Agent") buildNavItem( "My Approvals", () => handleTabChange( TabSelection.myApprovals, '/ApprovalList', ), layoutColor!, // () => context.go('/ApprovalList'), isSelected: selectedTab == TabSelection.myApprovals, icon: Icons.verified_outlined, ), ], ), ), Spacer(), ], ), ), actions: [ Padding( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.055, ), child: Row( children: [ // if (userData?["role"] != "User") ExcludeFocus( // Focus( // canRequestFocus: false, child:Builder( builder: (context) => PopupMenuButton( color: Colors.white, padding: EdgeInsets.zero, offset: const Offset( 0, 50, ), // 👈 shift it 50 pixels down onSelected: (String value) { switch (value) { case '/OrganizationSettings': context.go('/OrganizationSettings'); break; // case '/OrganizationSetup': // context.go('/OrganizationSetup'); // break; case '/listUser': context.go('/listUser'); break; case '/report': context.go('/report'); break; // case '/department': // context.go('/department'); // break; // case '/PolicyList': // context.go('/PolicyList'); // case '/getPerdiem': // context.go('/getPerdiem'); // case '/templateList': // context.go('/templateList'); // case '/template': // context.go('/template'); case '/CreateUserDetails': context.go( "/CreateUserDetails", extra: { "selectedUser": profileUserDetails, "isEditProfile": true, "isViewMode": false, }, ); case '/logout': logout(context); // context.go('/'); break; } }, // itemBuilder: (BuildContext context) => // menuItems.map(buildMenuItem).toList(), itemBuilder: (BuildContext context) { // final isUser = userData?["role"] == "User"; final role = userData?["role"]; List> filteredItems; if (role == "User") { filteredItems = menuItems .where( (item) => item['value'] == '/CreateUserDetails' || item['value'] == '/logout', ) .toList(); } else if (role == "Travel Agent") { filteredItems = menuItems .where((item) => item['value'] == '/logout') .toList(); } else { filteredItems = menuItems; } // Create a new list starting with role display and divider return [ PopupMenuItem( enabled: false, // ❌ Not clickable child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( userData?["role"] ?? '', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black, ), ), const PopupMenuDivider(), // const Divider(), // 👈 Divider after role ], ), ), ...filteredItems .map(buildMenuItem) .toList(), // 👈 then normal items // const PopupMenuDivider(), PopupMenuItem( enabled: false, // Not clickable child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Last Login : ", style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w500, color: Colors.grey, ), ), // SizedBox(width: 10), Text( "${userData?["last_login_at"] ?? ''}", style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w400, color: Colors.grey, ), ), ], ), ), ]; }, child: MouseRegion( cursor: SystemMouseCursors.click, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( userData?["name"] ?? "N/A", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black, ), // style: const TextStyle( // fontSize: 14, // fontWeight: FontWeight.w500, // fontFamily: "Roboto", // color: Colors.black, // ), ), const Icon( Icons.arrow_drop_down, size: 20, color: Colors.black87, ), ], ), ), ), ), ), const SizedBox(width: 8), ], ), ), ], bottom: PreferredSize( preferredSize: Size.fromHeight(1), child: Container( height: 1, // color: Colors.grey.shade200, // Set the color of the bottom border color: layoutColor, // Set the color of the bottom border ), ), ), ); } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); } final List> menuItems = [ // { // 'value': '/OrganizationSetup', // 'icon': Icons.business, // 'label': 'Organization' // }, { 'value': '/OrganizationSettings', 'icon': Icons.business, 'label': 'Org Management', }, { 'value': '/listUser', 'icon': Icons.manage_accounts, 'label': 'User Management', }, // {'value': '/group', 'icon': Icons.group, 'label': 'Group'}, // {'value': '/department', 'icon': Icons.group, 'label': 'Department'}, // {'value': '/PolicyList', 'icon': Icons.policy, 'label': 'Policy'}, // {'value': '/getPerdiem', 'icon': Icons.ac_unit_sharp, 'label': 'Forex'}, // { // 'value': '/templateList', // 'icon': Icons.ac_unit_sharp, // 'label': 'Template List' // }, // {'value': '/template', 'icon': Icons.ac_unit_sharp, 'label': 'Template'}, {'value': '/report', 'icon': Icons.auto_graph, 'label': 'Reports'}, { 'value': '/CreateUserDetails', 'icon': Icons.account_circle, 'label': 'My Profile', }, {'value': '/logout', 'icon': Icons.login_outlined, 'label': 'Logout'}, ]; PopupMenuItem buildMenuItem(Map item) { return PopupMenuItem( height: 40, // 👈 reduce PopupMenuItem height value: item['value'], padding: EdgeInsets.symmetric( horizontal: 12, ), // 👈 control left-right spacing child: Row( children: [ Icon( item['icon'], size: 18, color: Colors.black87, ), // 👈 smaller, cleaner icon SizedBox(width: 10), // 👈 small space between icon and text Text( item['label'], style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black87, ), ), ], ), ); } Widget buildNavItem( String label, VoidCallback onTap, Color? layoutColor, { bool isSelected = true, IconData? icon, }) { final effectiveColor = isSelected ? (layoutColor ?? Colors.blue) : Colors.black; return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: onTap, child: Stack( clipBehavior: Clip.none, children: [ Row( children: [ if (icon != null) Icon(icon, size: 16, color: effectiveColor), if (icon != null) const SizedBox(width: 4), Text( label, style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: effectiveColor, ), ), ], ), if (isSelected) AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, bottom: -20, left: 0, right: 0, child: AnimatedOpacity( duration: const Duration(milliseconds: 300), opacity: 1.0, child: AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, height: 2, width: isSelected ? 50 : 0, // Animate width (make sure isSelected changes) color: effectiveColor, ), ), ), ], ), ), ); }