1083 lines
35 KiB
Dart
1083 lines
35 KiB
Dart
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<AppHeader> createState() => _AppHeaderState();
|
|
|
|
@override
|
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
|
}
|
|
|
|
class _AppHeaderState extends ConsumerState<AppHeader> {
|
|
String? role;
|
|
ApiService apiService = ApiService();
|
|
Map<String, dynamic>? 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<void> _initializeToken() async {
|
|
_token = await AuthService.getToken();
|
|
if (_token != null) {
|
|
final Map<String, dynamic> decodedToken = Jwt.parseJwt(_token!);
|
|
setState(() {
|
|
profileData = decodedToken['data'];
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _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<void> 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<void> _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<String>(
|
|
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,
|
|
// ),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|