import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../l10n/app_strings.dart'; import '../providers/locale_provider.dart'; import '../theme/app_theme.dart'; import '../widgets/tooth_logo.dart'; import '../providers/auth_provider.dart'; import '../../models/tenant.dart'; import '../../features/auth/sign_in_screen.dart'; import '../../features/auth/sign_up_screen.dart'; import '../../features/auth/onboarding_screen.dart'; import '../../features/clinic/dashboard/clinic_dashboard_screen.dart'; import '../../features/clinic/jobs/clinic_jobs_screen.dart'; import '../../features/clinic/jobs/clinic_job_detail_screen.dart'; import '../../features/clinic/jobs/new_job_screen.dart'; import '../../features/clinic/patients/clinic_patients_screen.dart'; import '../../features/clinic/patients/clinic_patient_detail_screen.dart'; import '../../features/clinic/connections/clinic_connections_screen.dart'; import '../../features/clinic/finance/clinic_finance_screen.dart'; import '../../features/clinic/settings/clinic_settings_screen.dart'; import '../../features/lab/dashboard/lab_dashboard_screen.dart'; import '../../features/lab/jobs/lab_jobs_inbound_screen.dart'; import '../../features/lab/jobs/lab_all_jobs_screen.dart'; import '../../features/lab/jobs/lab_job_detail_screen.dart'; import '../../features/lab/products/lab_products_screen.dart'; import '../../features/lab/connections/lab_connections_screen.dart'; import '../../features/lab/finance/lab_finance_screen.dart'; import '../../features/lab/settings/lab_settings_screen.dart'; import '../../features/shared/reports_screen.dart'; import '../../features/shared/ai_chat_screen.dart'; import '../../features/lab/discounts/discounts_screen.dart'; import '../../features/lab/connections/connection_detail_screen.dart'; import '../../models/connection.dart'; // Auth routes const routeSignIn = '/sign-in'; const routeSignUp = '/sign-up'; const routeOnboarding = '/onboarding'; // Clinic routes const routeClinicDashboard = '/clinic/dashboard'; const routeClinicJobs = '/clinic/jobs'; const routeClinicJobDetail = '/clinic/jobs/:jobId'; const routeClinicJobNew = '/clinic/jobs/new'; const routeClinicPatients = '/clinic/patients'; const routeClinicPatientDetail = '/clinic/patients/:patientId'; const routeClinicConnections = '/clinic/connections'; const routeClinicFinance = '/clinic/finance'; const routeClinicSettings = '/clinic/settings'; const routeClinicReports = '/clinic/reports'; const routeClinicAi = '/clinic/ai'; // Lab routes const routeLabDashboard = '/lab/dashboard'; const routeLabJobsInbound = '/lab/jobs/inbound'; const routeLabJobsAll = '/lab/jobs'; const routeLabJobDetail = '/lab/jobs/:jobId'; const routeLabProducts = '/lab/products'; const routeLabConnections = '/lab/connections'; const routeLabFinance = '/lab/finance'; const routeLabSettings = '/lab/settings'; const routeLabReports = '/lab/reports'; const routeLabAi = '/lab/ai'; const routeLabDiscounts = '/lab/discounts'; List buildRoutes() => [ GoRoute(path: routeSignIn, builder: (_, __) => const SignInScreen()), GoRoute(path: routeSignUp, builder: (_, __) => const SignUpScreen()), GoRoute(path: routeOnboarding, builder: (_, __) => const OnboardingScreen()), // ── Clinic shell ────────────────────────────────────────────────────── ShellRoute( builder: (context, state, child) => _ClinicShell(child: child), routes: [ GoRoute(path: routeClinicDashboard, builder: (_, __) => const ClinicDashboardScreen()), GoRoute( path: routeClinicJobs, builder: (_, __) => const ClinicJobsScreen(), routes: [ GoRoute(path: 'new', builder: (_, __) => const NewJobScreen()), GoRoute( path: ':jobId', builder: (_, s) => ClinicJobDetailScreen(jobId: s.pathParameters['jobId']!), ), ], ), GoRoute( path: routeClinicPatients, builder: (_, __) => const ClinicPatientsScreen(), routes: [ GoRoute( path: ':patientId', builder: (_, s) => ClinicPatientDetailScreen(patientId: s.pathParameters['patientId']!), ), ], ), GoRoute(path: routeClinicConnections, builder: (_, __) => const ClinicConnectionsScreen()), GoRoute(path: routeClinicFinance, builder: (_, __) => const ClinicFinanceScreen()), GoRoute(path: routeClinicSettings, builder: (_, __) => const ClinicSettingsScreen()), GoRoute(path: routeClinicReports, builder: (_, __) => const ReportsScreen()), GoRoute(path: routeClinicAi, builder: (_, __) => const AiChatScreen()), ], ), // ── Lab shell ───────────────────────────────────────────────────────── ShellRoute( builder: (context, state, child) => _LabShell(child: child), routes: [ GoRoute(path: routeLabDashboard, builder: (_, __) => const LabDashboardScreen()), GoRoute(path: routeLabJobsInbound, builder: (_, __) => const LabJobsInboundScreen()), GoRoute( path: routeLabJobsAll, builder: (_, __) => const LabAllJobsScreen(), routes: [ GoRoute( path: ':jobId', builder: (_, s) => LabJobDetailScreen(jobId: s.pathParameters['jobId']!), ), ], ), GoRoute(path: routeLabProducts, builder: (_, __) => const LabProductsScreen()), GoRoute( path: routeLabConnections, builder: (_, __) => const LabConnectionsScreen(), routes: [ GoRoute( path: ':connectionId/detail', builder: (_, s) { final extra = s.extra as Map?; final connection = extra?['connection'] as Connection?; final labTenantId = extra?['labTenantId'] as String? ?? ''; if (connection == null) { return const Scaffold( body: Center(child: Text('Bağlantı bulunamadı')), ); } return ConnectionDetailScreen( connection: connection, labTenantId: labTenantId); }, ), ], ), GoRoute(path: routeLabDiscounts, builder: (_, __) => const DiscountsScreen()), GoRoute(path: routeLabFinance, builder: (_, __) => const LabFinanceScreen()), GoRoute(path: routeLabSettings, builder: (_, __) => const LabSettingsScreen()), GoRoute(path: routeLabReports, builder: (_, __) => const ReportsScreen()), GoRoute(path: routeLabAi, builder: (_, __) => const AiChatScreen()), ], ), ]; // ── Nav item descriptor ─────────────────────────────────────────────────────── class _NavItem { const _NavItem({ required this.route, required this.icon, required this.selectedIcon, required this.label, required this.visible, }); final String route; final Icon icon; final Icon selectedIcon; final String label; final bool Function(TenantMembership?) visible; } // ── Nav group (dropdown in sidebar) ─────────────────────────────────────────── class _NavGroup { const _NavGroup({ required this.title, required this.icon, required this.selectedIcon, required this.items, }); final String title; final IconData icon; final IconData selectedIcon; final List<_NavItem> items; /// Whether any item in this group is visible for the given membership bool hasVisible(TenantMembership? m) => items.any((it) => it.visible(m)); /// Whether any item in this group is currently selected bool containsRoute(String route) => items.any((it) => it.route == route); } // ── Sidebar entry (single item or group) ───────────────────────────────────── sealed class _SidebarEntry {} class _SidebarSingleEntry extends _SidebarEntry { final _NavItem item; _SidebarSingleEntry(this.item); } class _SidebarGroupEntry extends _SidebarEntry { final _NavGroup group; _SidebarGroupEntry(this.group); } // ── Clinic shell ────────────────────────────────────────────────────────────── class _ClinicShell extends ConsumerStatefulWidget { const _ClinicShell({required this.child}); final Widget child; @override ConsumerState<_ClinicShell> createState() => _ClinicShellState(); } class _ClinicShellState extends ConsumerState<_ClinicShell> { String _selectedRoute = routeClinicDashboard; List<_NavItem> _clinicTopSingles(AppStrings s) => [ _NavItem(route: routeClinicDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true), _NavItem(route: routeClinicJobs, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true), _NavItem(route: routeClinicPatients, icon: const Icon(Icons.people_outline_rounded), selectedIcon: const Icon(Icons.people_rounded), label: s.patientsTitle, visible: (m) => m?.showPatients ?? true), _NavItem(route: routeClinicFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true), _NavItem(route: routeClinicAi, icon: const Icon(Icons.auto_awesome_outlined), selectedIcon: const Icon(Icons.auto_awesome_rounded), label: s.aiAssistant, visible: (_) => true), ]; List<_NavGroup> _clinicGroups(AppStrings s) => [ _NavGroup( title: s.management, icon: Icons.tune_rounded, selectedIcon: Icons.tune_rounded, items: [ _NavItem(route: routeClinicConnections, icon: const Icon(Icons.link_rounded), selectedIcon: const Icon(Icons.link_rounded), label: s.connections, visible: (_) => true), _NavItem(route: routeClinicReports, icon: const Icon(Icons.bar_chart_outlined), selectedIcon: const Icon(Icons.bar_chart_rounded), label: s.reports, visible: (_) => true), ], ), ]; List<_NavItem> _clinicBottomSingles(AppStrings s) => [ _NavItem(route: routeClinicSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true), ]; List<_NavItem> _clinicMobileItems(AppStrings s) => [ _NavItem(route: routeClinicDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true), _NavItem(route: routeClinicJobs, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true), _NavItem(route: routeClinicPatients, icon: const Icon(Icons.people_outline_rounded), selectedIcon: const Icon(Icons.people_rounded), label: s.patientsTitle, visible: (m) => m?.showPatients ?? true), _NavItem(route: routeClinicFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true), _NavItem(route: routeClinicSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true), ]; List<_SidebarEntry> _allEntries(AppStrings s) { final membership = ref.read(authProvider).activeTenant; final entries = <_SidebarEntry>[]; for (final item in _clinicTopSingles(s)) { if (item.visible(membership)) entries.add(_SidebarSingleEntry(item)); } for (final group in _clinicGroups(s)) { if (group.hasVisible(membership)) entries.add(_SidebarGroupEntry(group)); } for (final item in _clinicBottomSingles(s)) { if (item.visible(membership)) entries.add(_SidebarSingleEntry(item)); } return entries; } @override Widget build(BuildContext context) { final s = ref.watch(stringsProvider); final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; if (isDesktop) { final entries = _allEntries(s); return Scaffold( backgroundColor: AppColors.background, body: Row( children: [ _DesktopSidebar( entries: entries, selectedRoute: _selectedRoute, onSelectRoute: (route) { setState(() => _selectedRoute = route); context.go(route); }, ), Expanded(child: widget.child), ], ), ); } // Mobile: only core items in bottom nav final membership = ref.read(authProvider).activeTenant; final items = _clinicMobileItems(s).where((it) => it.visible(membership)).toList(); final flatIndex = items.indexWhere((it) => it.route == _selectedRoute); final clampedIndex = flatIndex >= 0 ? flatIndex : 0; return Scaffold( body: widget.child, floatingActionButton: FloatingActionButton.small( heroTag: 'ai_fab_clinic', backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 3, onPressed: () { setState(() => _selectedRoute = routeClinicAi); context.go(routeClinicAi); }, child: const Icon(Icons.auto_awesome_rounded, size: 20), ), bottomNavigationBar: NavigationBar( selectedIndex: clampedIndex.clamp(0, items.length - 1), onDestinationSelected: (i) { setState(() => _selectedRoute = items[i].route); context.go(items[i].route); }, destinations: [ for (final it in items) Semantics( label: it.label, button: true, child: NavigationDestination(icon: it.icon, selectedIcon: it.selectedIcon, label: it.label), ), ], ), ); } } // ── Lab shell ───────────────────────────────────────────────────────────────── class _LabShell extends ConsumerStatefulWidget { const _LabShell({required this.child}); final Widget child; @override ConsumerState<_LabShell> createState() => _LabShellState(); } class _LabShellState extends ConsumerState<_LabShell> { String _selectedRoute = routeLabDashboard; List<_NavItem> _labTopSingles(AppStrings s) => [ _NavItem(route: routeLabDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true), _NavItem(route: routeLabJobsAll, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true), _NavItem(route: routeLabProducts, icon: const Icon(Icons.inventory_2_outlined), selectedIcon: const Icon(Icons.inventory_2_rounded), label: s.productsTitle, visible: (m) => m?.showProducts ?? true), _NavItem(route: routeLabFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true), _NavItem(route: routeLabAi, icon: const Icon(Icons.auto_awesome_outlined), selectedIcon: const Icon(Icons.auto_awesome_rounded), label: s.aiAssistant, visible: (_) => true), ]; List<_NavGroup> _labGroups(AppStrings s) => [ _NavGroup( title: s.management, icon: Icons.tune_rounded, selectedIcon: Icons.tune_rounded, items: [ _NavItem(route: routeLabConnections, icon: const Icon(Icons.link_rounded), selectedIcon: const Icon(Icons.link_rounded), label: s.connections, visible: (_) => true), _NavItem(route: routeLabDiscounts, icon: const Icon(Icons.local_offer_outlined), selectedIcon: const Icon(Icons.local_offer_rounded), label: s.discounts, visible: (_) => true), _NavItem(route: routeLabReports, icon: const Icon(Icons.bar_chart_outlined), selectedIcon: const Icon(Icons.bar_chart_rounded), label: s.reports, visible: (_) => true), ], ), ]; List<_NavItem> _labBottomSingles(AppStrings s) => [ _NavItem(route: routeLabSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true), ]; List<_NavItem> _labMobileItems(AppStrings s) => [ _NavItem(route: routeLabDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true), _NavItem(route: routeLabJobsAll, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true), _NavItem(route: routeLabProducts, icon: const Icon(Icons.inventory_2_outlined), selectedIcon: const Icon(Icons.inventory_2_rounded), label: s.productsTitle, visible: (m) => m?.showProducts ?? true), _NavItem(route: routeLabFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true), _NavItem(route: routeLabSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true), ]; List<_SidebarEntry> _allEntries(AppStrings s) { final membership = ref.read(authProvider).activeTenant; final entries = <_SidebarEntry>[]; for (final item in _labTopSingles(s)) { if (item.visible(membership)) entries.add(_SidebarSingleEntry(item)); } for (final group in _labGroups(s)) { if (group.hasVisible(membership)) entries.add(_SidebarGroupEntry(group)); } for (final item in _labBottomSingles(s)) { if (item.visible(membership)) entries.add(_SidebarSingleEntry(item)); } return entries; } @override Widget build(BuildContext context) { final s = ref.watch(stringsProvider); final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; if (isDesktop) { final entries = _allEntries(s); return Scaffold( backgroundColor: AppColors.background, body: Row( children: [ _DesktopSidebar( entries: entries, selectedRoute: _selectedRoute, onSelectRoute: (route) { setState(() => _selectedRoute = route); context.go(route); }, ), Expanded(child: widget.child), ], ), ); } // Mobile: only core items in bottom nav final membership = ref.read(authProvider).activeTenant; final items = _labMobileItems(s).where((it) => it.visible(membership)).toList(); final flatIndex = items.indexWhere((it) => it.route == _selectedRoute); final clampedIndex = flatIndex >= 0 ? flatIndex : 0; return Scaffold( body: widget.child, floatingActionButton: FloatingActionButton.small( heroTag: 'ai_fab_lab', backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 3, onPressed: () { setState(() => _selectedRoute = routeLabAi); context.go(routeLabAi); }, child: const Icon(Icons.auto_awesome_rounded, size: 20), ), bottomNavigationBar: NavigationBar( selectedIndex: clampedIndex.clamp(0, items.length - 1), onDestinationSelected: (i) { setState(() => _selectedRoute = items[i].route); context.go(items[i].route); }, destinations: [ for (final it in items) Semantics( label: it.label, button: true, child: NavigationDestination(icon: it.icon, selectedIcon: it.selectedIcon, label: it.label), ), ], ), ); } } // ── Desktop sidebar ─────────────────────────────────────────────────────────── class _DesktopSidebar extends StatefulWidget { const _DesktopSidebar({ required this.entries, required this.selectedRoute, required this.onSelectRoute, }); final List<_SidebarEntry> entries; final String selectedRoute; final ValueChanged onSelectRoute; // Must match the toolbarHeight used in desktop SliverAppBar headers static const double headerHeight = 64; static const double _openWidth = 220; static const double _closedWidth = 64; @override State<_DesktopSidebar> createState() => _DesktopSidebarState(); } class _DesktopSidebarState extends State<_DesktopSidebar> { bool _open = true; @override Widget build(BuildContext context) { return AnimatedContainer( duration: const Duration(milliseconds: 220), curve: Curves.easeInOut, width: _open ? _DesktopSidebar._openWidth : _DesktopSidebar._closedWidth, decoration: const BoxDecoration( color: AppColors.surface, border: Border(right: BorderSide(color: AppColors.border)), boxShadow: [BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(2, 0))], ), child: ClipRect( child: Column( children: [ // Header Container( height: _DesktopSidebar.headerHeight, decoration: const BoxDecoration( gradient: LinearGradient(colors: [AppColors.primary, AppColors.accent]), border: Border(bottom: BorderSide(color: AppColors.border)), ), padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(9), border: Border.all(color: Colors.white.withValues(alpha: 0.25)), ), child: const Center(child: ToothLogo(size: 18, color: Colors.white)), ), if (_open) ...[ const SizedBox(width: 10), const Text( 'DLS', style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.w800, letterSpacing: 1), ), ], ], ), ), // Nav entries (singles + groups) Expanded( child: SingleChildScrollView( child: Column( children: [ const SizedBox(height: 8), for (final entry in widget.entries) _buildEntry(entry), const SizedBox(height: 8), ], ), ), ), // Toggle button Container( decoration: const BoxDecoration( border: Border(top: BorderSide(color: AppColors.border)), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () => setState(() => _open = !_open), child: SizedBox( height: 48, child: Row( mainAxisAlignment: _open ? MainAxisAlignment.start : MainAxisAlignment.center, children: [ if (_open) const SizedBox(width: 20), AnimatedRotation( duration: const Duration(milliseconds: 220), turns: _open ? 0.5 : 0, child: const Icon(Icons.chevron_right_rounded, color: AppColors.textMuted, size: 20), ), if (_open) ...[ const SizedBox(width: 8), const Text('Daralt', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: AppColors.textMuted)), ], ], ), ), ), ), ), ], ), ), ); } Widget _buildEntry(_SidebarEntry entry) { if (entry case final _SidebarSingleEntry single) { return _SidebarItem( icon: single.item.icon, selectedIcon: single.item.selectedIcon, label: single.item.label, selected: widget.selectedRoute == single.item.route, open: _open, onTap: () => widget.onSelectRoute(single.item.route), ); } else if (entry case final _SidebarGroupEntry group) { return _SidebarGroup( group: group.group, selectedRoute: widget.selectedRoute, open: _open, onSelectRoute: widget.onSelectRoute, ); } return const SizedBox.shrink(); } } // ── Sidebar nav item ────────────────────────────────────────────────────────── class _SidebarItem extends StatelessWidget { const _SidebarItem({ required this.icon, required this.selectedIcon, required this.label, required this.selected, required this.open, required this.onTap, }); final Widget icon; final Widget selectedIcon; final String label; final bool selected; final bool open; final VoidCallback onTap; @override Widget build(BuildContext context) { final item = Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Material( color: selected ? const Color(0xFFDBEAFE) : Colors.transparent, borderRadius: BorderRadius.circular(10), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(10), child: SizedBox( height: 40, child: open ? Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ IconTheme( data: IconThemeData( color: selected ? AppColors.primary : AppColors.textSecondary, size: 20, ), child: selected ? selectedIcon : icon, ), const SizedBox(width: 12), Text( label, style: TextStyle( fontSize: 14, fontWeight: selected ? FontWeight.w600 : FontWeight.w500, color: selected ? AppColors.primary : AppColors.textSecondary, ), ), ], ), ) : Center( child: IconTheme( data: IconThemeData( color: selected ? AppColors.primary : AppColors.textSecondary, size: 20, ), child: selected ? selectedIcon : icon, ), ), ), ), ), ); if (!open) { return Tooltip(message: label, preferBelow: false, child: item); } return item; } } // ── Sidebar group (accordion dropdown) ──────────────────────────────────────── class _SidebarGroup extends StatefulWidget { const _SidebarGroup({ required this.group, required this.selectedRoute, required this.open, required this.onSelectRoute, }); final _NavGroup group; final String selectedRoute; final bool open; final ValueChanged onSelectRoute; @override State<_SidebarGroup> createState() => _SidebarGroupState(); } class _SidebarGroupState extends State<_SidebarGroup> { bool _expanded = false; @override void initState() { super.initState(); // Auto-expand if any child is selected if (widget.group.containsRoute(widget.selectedRoute)) { _expanded = true; } } @override void didUpdateWidget(_SidebarGroup old) { super.didUpdateWidget(old); if (widget.group.containsRoute(widget.selectedRoute) && !_expanded) { setState(() => _expanded = true); } } @override Widget build(BuildContext context) { final isSelected = widget.group.containsRoute(widget.selectedRoute); if (!widget.open) { // Collapsed sidebar: show group icon only, tooltip with title return Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Tooltip( message: widget.group.title, preferBelow: false, child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(10), child: InkWell( onTap: () { // Toggle expanded and show first visible item if (!_expanded) { setState(() => _expanded = true); } // If already expanded, navigate to first item final first = widget.group.items.firstWhere( (it) => true, orElse: () => widget.group.items.first, ); widget.onSelectRoute(first.route); }, borderRadius: BorderRadius.circular(10), child: SizedBox( height: 40, child: Center( child: Icon( isSelected ? widget.group.selectedIcon : widget.group.icon, size: 20, color: isSelected ? AppColors.primary : AppColors.textSecondary, ), ), ), ), ), ), ); } return Column( children: [ // Group header (clickable to expand/collapse) Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(10), child: InkWell( onTap: () => setState(() => _expanded = !_expanded), borderRadius: BorderRadius.circular(10), child: SizedBox( height: 40, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ Icon( isSelected ? widget.group.selectedIcon : widget.group.icon, size: 20, color: isSelected ? AppColors.primary : AppColors.textSecondary, ), const SizedBox(width: 12), Expanded( child: Text( widget.group.title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ), AnimatedRotation( duration: const Duration(milliseconds: 200), turns: _expanded ? 0.5 : 0, child: const Icon( Icons.keyboard_arrow_down_rounded, size: 18, color: AppColors.textMuted, ), ), ], ), ), ), ), ), ), // Sub-items (animated expand/collapse) AnimatedCrossFade( duration: const Duration(milliseconds: 200), crossFadeState: _expanded ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: Column( children: [ for (final item in widget.group.items) Padding( padding: const EdgeInsets.only(left: 20), child: _SidebarItem( icon: item.icon, selectedIcon: item.selectedIcon, label: item.label, selected: widget.selectedRoute == item.route, open: true, onTap: () => widget.onSelectRoute(item.route), ), ), ], ), secondChild: const SizedBox(width: double.infinity, height: 0), ), ], ); } }