import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../providers/auth_provider.dart'; import '../providers/notifications_provider.dart'; import '../providers/profile_provider.dart'; class AppScaffold extends ConsumerWidget { const AppScaffold({super.key, required this.child}); final Widget child; @override Widget build(BuildContext context, WidgetRef ref) { final profileAsync = ref.watch(currentProfileProvider); final role = profileAsync.maybeWhen( data: (profile) => profile?.role ?? 'standard', orElse: () => 'standard', ); final displayName = profileAsync.maybeWhen( data: (profile) { final name = profile?.fullName.trim() ?? ''; return name.isNotEmpty ? name : 'User'; }, orElse: () => 'User', ); final isStandard = role == 'standard'; final location = GoRouterState.of(context).uri.toString(); final sections = _buildSections(role); final width = MediaQuery.of(context).size.width; final showRail = !isStandard && width >= 860; final isExtended = !isStandard && width >= 1120; final showDrawer = !isStandard && !showRail; return Scaffold( appBar: AppBar( title: Row( children: [ const Icon(Icons.memory), const SizedBox(width: 8), Text('TasQ'), ], ), actions: [ if (isStandard) PopupMenuButton( tooltip: 'Account', onSelected: (value) { if (value == 0) { ref.read(authControllerProvider).signOut(); } }, itemBuilder: (context) => const [ PopupMenuItem(value: 0, child: Text('Sign out')), ], child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ const Icon(Icons.account_circle), const SizedBox(width: 8), Text(displayName), const SizedBox(width: 4), const Icon(Icons.expand_more), ], ), ), ) else IconButton( tooltip: 'Sign out', onPressed: () => ref.read(authControllerProvider).signOut(), icon: const Icon(Icons.logout), ), const _NotificationBell(), ], ), drawer: showDrawer ? Drawer( child: AppSideNav( sections: sections, location: location, extended: true, displayName: displayName, onLogout: () => ref.read(authControllerProvider).signOut(), ), ) : null, bottomNavigationBar: isStandard ? AppBottomNav(location: location, items: _standardNavItems()) : null, body: Row( children: [ if (showRail) AppSideNav( sections: sections, location: location, extended: isExtended, displayName: displayName, onLogout: () => ref.read(authControllerProvider).signOut(), ), Expanded(child: _ShellBackground(child: child)), ], ), ); } } class AppSideNav extends StatelessWidget { const AppSideNav({ super.key, required this.sections, required this.location, required this.extended, required this.displayName, required this.onLogout, }); final List sections; final String location; final bool extended; final String displayName; final VoidCallback onLogout; @override Widget build(BuildContext context) { final width = extended ? 240.0 : 72.0; return Container( width: width, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, border: Border( right: BorderSide( color: Theme.of(context).colorScheme.outlineVariant, ), ), ), child: ListView( padding: const EdgeInsets.symmetric(vertical: 12), children: [ Padding( padding: EdgeInsets.symmetric( horizontal: extended ? 16 : 12, vertical: 8, ), child: Row( children: [ Image.asset( 'assets/tasq_ico.png', width: 28, height: 28, fit: BoxFit.contain, ), if (extended) ...[ const SizedBox(width: 12), Expanded( child: Text( displayName, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ), ], ], ), ), const SizedBox(height: 4), for (final section in sections) ...[ if (section.label != null && extended) Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: Text( section.label!, style: Theme.of(context).textTheme.labelMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, letterSpacing: 0.4, ), ), ), for (final item in section.items) _NavTile( item: item, selected: _isSelected(location, item.route), extended: extended, onLogout: onLogout, ), ], ], ), ); } } class AppBottomNav extends StatelessWidget { const AppBottomNav({super.key, required this.location, required this.items}); final String location; final List items; @override Widget build(BuildContext context) { final index = _currentIndex(location, items); return NavigationBar( selectedIndex: index, onDestinationSelected: (value) { final target = items[value].route; if (target.isNotEmpty) { context.go(target); } }, destinations: [ for (final item in items) NavigationDestination( icon: Icon(item.icon), selectedIcon: Icon(item.selectedIcon ?? item.icon), label: item.label, ), ], ); } } class _NavTile extends StatelessWidget { const _NavTile({ required this.item, required this.selected, required this.extended, required this.onLogout, }); final NavItem item; final bool selected; final bool extended; final VoidCallback onLogout; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final iconColor = selected ? colorScheme.primary : colorScheme.onSurface; final background = selected ? colorScheme.primaryContainer.withValues(alpha: 0.6) : Colors.transparent; final content = Container( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: background, borderRadius: BorderRadius.circular(12), ), child: ListTile( leading: Icon(item.icon, color: iconColor), title: extended ? Text(item.label) : null, onTap: () => item.onTap(context, onLogout: onLogout), dense: true, visualDensity: VisualDensity.compact, ), ); if (extended) { return content; } return Tooltip(message: item.label, child: content); } } class _NotificationBell extends ConsumerWidget { const _NotificationBell(); @override Widget build(BuildContext context, WidgetRef ref) { final unreadCount = ref.watch(unreadNotificationsCountProvider); return IconButton( tooltip: 'Notifications', onPressed: () => context.go('/notifications'), icon: Stack( clipBehavior: Clip.none, children: [ const Icon(Icons.notifications), if (unreadCount > 0) const Positioned( right: -2, top: -2, child: Icon(Icons.circle, size: 10, color: Colors.red), ), ], ), ); } } class _ShellBackground extends StatelessWidget { const _ShellBackground({required this.child}); final Widget child; @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surfaceContainerLowest, ], ), ), child: child, ); } } class NavItem { NavItem({ required this.label, required this.route, required this.icon, this.selectedIcon, this.isLogout = false, }); final String label; final String route; final IconData icon; final IconData? selectedIcon; final bool isLogout; void onTap(BuildContext context, {VoidCallback? onLogout}) { if (isLogout) { onLogout?.call(); return; } if (route.isNotEmpty) { context.go(route); } } } class NavSection { NavSection({this.label, required this.items}); final String? label; final List items; } List _buildSections(String role) { final mainItems = [ NavItem( label: 'Dashboard', route: '/dashboard', icon: Icons.grid_view, selectedIcon: Icons.grid_view_rounded, ), NavItem( label: 'Tickets', route: '/tickets', icon: Icons.support_agent_outlined, selectedIcon: Icons.support_agent, ), NavItem( label: 'Tasks', route: '/tasks', icon: Icons.task_outlined, selectedIcon: Icons.task, ), NavItem( label: 'Events', route: '/events', icon: Icons.event_outlined, selectedIcon: Icons.event, ), NavItem( label: 'Announcement', route: '/announcements', icon: Icons.campaign_outlined, selectedIcon: Icons.campaign, ), NavItem( label: 'Workforce', route: '/workforce', icon: Icons.groups_outlined, selectedIcon: Icons.groups, ), NavItem( label: 'Reports', route: '/reports', icon: Icons.analytics_outlined, selectedIcon: Icons.analytics, ), ]; if (role == 'admin') { return [ NavSection(label: 'Operations', items: mainItems), NavSection( label: 'Settings', items: [ NavItem( label: 'User Management', route: '/settings/users', icon: Icons.admin_panel_settings_outlined, selectedIcon: Icons.admin_panel_settings, ), NavItem( label: 'Office Management', route: '/settings/offices', icon: Icons.apartment_outlined, selectedIcon: Icons.apartment, ), NavItem( label: 'Logout', route: '', icon: Icons.logout, isLogout: true, ), ], ), ]; } return [NavSection(label: 'Operations', items: mainItems)]; } List _standardNavItems() { return [ NavItem( label: 'Dashboard', route: '/dashboard', icon: Icons.grid_view, selectedIcon: Icons.grid_view_rounded, ), NavItem( label: 'Tickets', route: '/tickets', icon: Icons.support_agent_outlined, selectedIcon: Icons.support_agent, ), NavItem( label: 'Tasks', route: '/tasks', icon: Icons.task_outlined, selectedIcon: Icons.task, ), NavItem( label: 'Events', route: '/events', icon: Icons.event_outlined, selectedIcon: Icons.event, ), ]; } bool _isSelected(String location, String route) { if (route.isEmpty) return false; if (location == route) return true; return location.startsWith('$route/'); } int _currentIndex(String location, List items) { final index = items.indexWhere((item) => _isSelected(location, item.route)); return index == -1 ? 0 : index; }