ts-tat/lib/routes/custom_appBar.dart
2025-07-18 15:07:42 +05:30

786 lines
28 KiB
Dart

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<Widget>? 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<CustomAppBar> {
final ApiService apiService = ApiService();
late TabSelection selectedTab;
String? token;
Map<String, dynamic>? userData;
Map<String, dynamic>? fetchedUserData;
Map<String, dynamic> userDetails = {};
Map<String, dynamic> profileUserDetails = {};
Map<String, dynamic>? 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<void> 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<void> 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<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString("auth_token");
}
Future<Map<String, dynamic>?> 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<void> 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<String, dynamic> 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<void> 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<void> 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<String>(
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<Map<String, dynamic>> 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<String>(
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<String>(
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<Map<String, dynamic>> 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<String> buildMenuItem(Map<String, dynamic> item) {
return PopupMenuItem<String>(
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,
),
),
),
],
),
),
);
}