import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:geosector_app/presentation/widgets/dashboard_layout.dart'; import 'package:geosector_app/presentation/widgets/badged_navigation_destination.dart'; import 'package:geosector_app/core/services/current_user_service.dart'; import 'package:geosector_app/app.dart'; import 'dart:math' as math; /// Classe pour dessiner les petits points blancs sur le fond class DotsPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.white.withOpacity(0.5) ..style = PaintingStyle.fill; final random = math.Random(42); // Seed fixe pour consistance final numberOfDots = (size.width * size.height) ~/ 1500; for (int i = 0; i < numberOfDots; i++) { final x = random.nextDouble() * size.width; final y = random.nextDouble() * size.height; final radius = 1.0 + random.nextDouble() * 2.0; canvas.drawCircle(Offset(x, y), radius, paint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } /// Scaffold unifié pour toutes les pages (admin et user) /// Adapte automatiquement son apparence selon le rôle de l'utilisateur class AppScaffold extends StatelessWidget { /// Le contenu de la page final Widget body; /// L'index de navigation sélectionné final int selectedIndex; /// Le titre de la page final String pageTitle; /// Callback optionnel pour gérer la navigation personnalisée final Function(int)? onDestinationSelected; /// Forcer le mode admin (optionnel, sinon détecte automatiquement) final bool? forceAdmin; /// Afficher ou non le fond dégradé avec points (économise des ressources si désactivé) final bool showBackground; const AppScaffold({ super.key, required this.body, required this.selectedIndex, required this.pageTitle, this.onDestinationSelected, this.forceAdmin, this.showBackground = true, }); @override Widget build(BuildContext context) { final currentUser = userRepository.getCurrentUser(); final size = MediaQuery.of(context).size; final isMobile = size.width <= 900; // Déterminer si l'utilisateur est admin (prend en compte le mode d'affichage) final userRole = currentUser?.role ?? 1; final isAdmin = forceAdmin ?? CurrentUserService.instance.shouldShowAdminUI; debugPrint('🎨 AppScaffold: isAdmin=$isAdmin, displayMode=${CurrentUserService.instance.displayMode}, userRole=$userRole'); // Pour les utilisateurs standards, vérifier les conditions d'accès if (!isAdmin) { final hasOperation = userRepository.getCurrentOperation() != null; final hasSectors = userRepository.getUserSectors().isNotEmpty; // Si pas d'opération, afficher le message approprié if (!hasOperation) { return _buildRestrictedAccess( context: context, icon: Icons.warning_outlined, title: 'Aucune opération assignée', message: 'Vous n\'avez pas encore été affecté à une opération. ' 'Veuillez contacter votre administrateur pour obtenir un accès.', isAdmin: false, ); } // Si pas de secteur, afficher le message approprié if (!hasSectors) { return _buildRestrictedAccess( context: context, icon: Icons.map_outlined, title: 'Aucun secteur assigné', message: 'Vous n\'êtes affecté sur aucun secteur. ' 'Contactez votre administrateur pour qu\'il vous en affecte au moins un.', isAdmin: false, ); } } // Couleurs de fond selon le rôle final gradientColors = isAdmin ? [Colors.white, Colors.red.shade300] // Admin: dégradé rouge : [Colors.white, Colors.green.shade300]; // User: dégradé vert // Titre avec suffixe selon le rôle final dashboardTitle = isAdmin ? 'Tableau de bord Administration' : 'GEOSECTOR'; return Stack( children: [ // Fond dégradé avec petits points blancs (optionnel) if (showBackground) Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: gradientColors, ), ), child: CustomPaint( painter: DotsPainter(), child: const SizedBox(width: double.infinity, height: double.infinity), ), ), // Contenu de la page avec navigation DashboardLayout( key: ValueKey('dashboard_layout_${isAdmin ? 'admin' : 'user'}_$selectedIndex'), title: dashboardTitle, selectedIndex: selectedIndex, onDestinationSelected: onDestinationSelected ?? (index) { NavigationHelper.navigateToIndex(context, index, isAdmin); }, destinations: NavigationHelper.getDestinations( isAdmin: isAdmin, isMobile: isMobile, ), isAdmin: isAdmin, body: body, ), ], ); } /// Construit l'écran d'accès restreint Widget _buildRestrictedAccess({ required BuildContext context, required IconData icon, required String title, required String message, required bool isAdmin, }) { final theme = Theme.of(context); // Utiliser le même fond que pour un utilisateur normal (vert) final gradientColors = isAdmin ? [Colors.white, Colors.red.shade300] : [Colors.white, Colors.green.shade300]; return Stack( children: [ // Fond dégradé (optionnel) if (showBackground) Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: gradientColors, ), ), child: CustomPaint( painter: DotsPainter(), child: const SizedBox(width: double.infinity, height: double.infinity), ), ), // Message d'accès restreint DashboardLayout( title: 'GEOSECTOR', selectedIndex: 0, onDestinationSelected: (index) { // Ne rien faire car l'utilisateur ne peut pas naviguer }, destinations: const [ NavigationDestination( icon: Icon(Icons.warning_outlined), selectedIcon: Icon(Icons.warning), label: 'Accès restreint', ), ], isAdmin: false, body: Center( child: Container( padding: const EdgeInsets.all(24), constraints: const BoxConstraints(maxWidth: 500), decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: theme.shadowColor.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 80, color: theme.colorScheme.error, ), const SizedBox(height: 24), Text( title, style: theme.textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), textAlign: TextAlign.center, ), const SizedBox(height: 16), Text( message, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), ), textAlign: TextAlign.center, ), ], ), ), ), ), ], ); } } /// Helper centralisé pour la navigation class NavigationHelper { /// Obtenir la liste des destinations selon le mode d'affichage et le device static List getDestinations({ required bool isAdmin, required bool isMobile, }) { final destinations = []; // Pages communes à tous les rôles destinations.addAll([ const NavigationDestination( icon: Icon(Icons.dashboard_outlined), selectedIcon: Icon(Icons.dashboard), label: 'Tableau de bord', ), const NavigationDestination( icon: Icon(Icons.history_outlined), selectedIcon: Icon(Icons.history), label: 'Historique', ), const NavigationDestination( icon: Icon(Icons.map_outlined), selectedIcon: Icon(Icons.map), label: 'Carte', ), createBadgedNavigationDestination( icon: const Icon(Icons.chat_outlined), selectedIcon: const Icon(Icons.chat), label: 'Messages', showBadge: true, ), ]); // Pages spécifiques aux utilisateurs standards if (!isAdmin) { destinations.add( const NavigationDestination( icon: Icon(Icons.explore_outlined), selectedIcon: Icon(Icons.explore), label: 'Terrain', ), ); } // Pages spécifiques aux admins (seulement sur desktop) if (isAdmin && !isMobile) { destinations.addAll([ const NavigationDestination( icon: Icon(Icons.business_outlined), selectedIcon: Icon(Icons.business), label: 'Amicale & membres', ), const NavigationDestination( icon: Icon(Icons.calendar_today_outlined), selectedIcon: Icon(Icons.calendar_today), label: 'Opérations', ), ]); } return destinations; } /// Naviguer vers une page selon l'index et le rôle static void navigateToIndex(BuildContext context, int index, bool isAdmin) { if (isAdmin) { _navigateAdminIndex(context, index); } else { _navigateUserIndex(context, index); } } /// Navigation pour les admins static void _navigateAdminIndex(BuildContext context, int index) { switch (index) { case 0: context.go('/admin'); break; case 1: context.go('/admin/history'); break; case 2: context.go('/admin/map'); break; case 3: context.go('/admin/messages'); break; case 4: context.go('/admin/amicale'); break; case 5: context.go('/admin/operations'); break; default: context.go('/admin'); } } /// Navigation pour les utilisateurs standards static void _navigateUserIndex(BuildContext context, int index) { switch (index) { case 0: context.go('/user/dashboard'); break; case 1: context.go('/user/history'); break; case 2: context.go('/user/map'); break; case 3: context.go('/user/messages'); break; case 4: context.go('/user/field-mode'); break; default: context.go('/user/dashboard'); } } /// Obtenir l'index selon la route actuelle et le rôle static int getIndexFromRoute(String route, bool isAdmin) { // Enlever les paramètres de query si présents final cleanRoute = route.split('?').first; if (isAdmin) { if (cleanRoute.contains('/admin/history')) return 1; if (cleanRoute.contains('/admin/map')) return 2; if (cleanRoute.contains('/admin/messages')) return 3; if (cleanRoute.contains('/admin/amicale')) return 4; if (cleanRoute.contains('/admin/operations')) return 5; return 0; // Dashboard par défaut } else { if (cleanRoute.contains('/user/history')) return 1; if (cleanRoute.contains('/user/map')) return 2; if (cleanRoute.contains('/user/messages')) return 3; if (cleanRoute.contains('/user/field-mode')) return 4; return 0; // Dashboard par défaut } } /// Obtenir le nom de la page selon l'index et le rôle static String getPageNameFromIndex(int index, bool isAdmin) { if (isAdmin) { switch (index) { case 0: return 'dashboard'; case 1: return 'history'; case 2: return 'map'; case 3: return 'messages'; case 4: return 'amicale'; case 5: return 'operations'; default: return 'dashboard'; } } else { switch (index) { case 0: return 'dashboard'; case 1: return 'history'; case 2: return 'map'; case 3: return 'messages'; case 4: return 'field-mode'; default: return 'dashboard'; } } } }