import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.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; } // ── Clinic shell ────────────────────────────────────────────────────────────── class _ClinicShell extends ConsumerStatefulWidget { const _ClinicShell({required this.child}); final Widget child; @override ConsumerState<_ClinicShell> createState() => _ClinicShellState(); } class _ClinicShellState extends ConsumerState<_ClinicShell> { int _index = 0; static final _allItems = [ _NavItem(route: routeClinicDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: 'Ana Sayfa', visible: (_) => true), _NavItem(route: routeClinicJobs, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: 'İşler', visible: (m) => m?.showJobs ?? true), _NavItem(route: routeClinicPatients, icon: const Icon(Icons.people_outline_rounded), selectedIcon: const Icon(Icons.people_rounded), label: 'Hastalar', visible: (m) => m?.showPatients ?? true), _NavItem(route: routeClinicFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: 'Finans', visible: (m) => m?.showFinance ?? true), _NavItem(route: routeClinicSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: 'Ayarlar', visible: (_) => true), ]; @override Widget build(BuildContext context) { final membership = ref.watch(authProvider).activeTenant; final items = _allItems.where((it) => it.visible(membership)).toList(); final clampedIndex = _index.clamp(0, items.length - 1); final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; void onTap(int i) { setState(() => _index = i); context.go(items[i].route); } if (isDesktop) { return Scaffold( backgroundColor: AppColors.background, body: Row( children: [ _DesktopSidebar(destinations: items, selectedIndex: clampedIndex, onTap: onTap), Expanded(child: widget.child), ], ), ); } return Scaffold( body: widget.child, bottomNavigationBar: NavigationBar( selectedIndex: clampedIndex, onDestinationSelected: onTap, 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> { int _index = 0; static final _allItems = [ _NavItem(route: routeLabDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: 'Ana Sayfa', visible: (_) => true), _NavItem(route: routeLabJobsAll, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: 'İşler', visible: (m) => m?.showJobs ?? true), _NavItem(route: routeLabProducts, icon: const Icon(Icons.inventory_2_outlined), selectedIcon: const Icon(Icons.inventory_2_rounded), label: 'Ürünler', visible: (m) => m?.showProducts ?? true), _NavItem(route: routeLabFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: 'Finans', visible: (m) => m?.showFinance ?? true), _NavItem(route: routeLabSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: 'Ayarlar', visible: (_) => true), ]; @override Widget build(BuildContext context) { final membership = ref.watch(authProvider).activeTenant; final items = _allItems.where((it) => it.visible(membership)).toList(); final clampedIndex = _index.clamp(0, items.length - 1); final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; void onTap(int i) { setState(() => _index = i); context.go(items[i].route); } if (isDesktop) { return Scaffold( backgroundColor: AppColors.background, body: Row( children: [ _DesktopSidebar(destinations: items, selectedIndex: clampedIndex, onTap: onTap), Expanded(child: widget.child), ], ), ); } return Scaffold( body: widget.child, bottomNavigationBar: NavigationBar( selectedIndex: clampedIndex, onDestinationSelected: onTap, 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.destinations, required this.selectedIndex, required this.onTap, }); final List<_NavItem> destinations; final int selectedIndex; final ValueChanged onTap; // 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 items Expanded( child: SingleChildScrollView( child: Column( children: [ const SizedBox(height: 8), for (int i = 0; i < widget.destinations.length; i++) _SidebarItem( icon: widget.destinations[i].icon, selectedIcon: widget.destinations[i].selectedIcon, label: widget.destinations[i].label, selected: widget.selectedIndex == i, open: _open, onTap: () => widget.onTap(i), ), 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)), ], ], ), ), ), ), ), ], ), ), ); } } // ── 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; } }