You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

607 lines
21 KiB

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Views/Dashboard.dart';
import 'package:youmazgestion/Views/HandleProduct.dart';
import 'package:youmazgestion/Views/RoleListPage.dart';
import 'package:youmazgestion/Views/commandManagement.dart';
import 'package:youmazgestion/Views/historique.dart';
import 'package:youmazgestion/Views/bilanMois.dart';
import 'package:youmazgestion/Views/gestionStock.dart';
import 'package:youmazgestion/Views/listUser.dart';
import 'package:youmazgestion/Views/loginPage.dart';
import 'package:youmazgestion/Views/newCommand.dart';
import 'package:youmazgestion/Views/registrationPage.dart';
import 'package:youmazgestion/accueil.dart';
import 'package:youmazgestion/controller/userController.dart';
import 'package:youmazgestion/Views/gestion_point_de_vente.dart';
class CustomDrawer extends StatelessWidget {
final UserController userController = Get.find<UserController>();
Future<void> clearUserData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('username');
await prefs.remove('role');
await prefs.remove('user_id');
// ✅ IMPORTANT: Vider le cache de session
userController.clearUserData();
}
CustomDrawer({super.key});
@override
Widget build(BuildContext context) {
return Drawer(
backgroundColor: Colors.white,
child: GetBuilder<UserController>(
builder: (controller) {
return ListView(
padding: EdgeInsets.zero,
children: [
// Header utilisateur
_buildUserHeader(controller),
// ✅ CORRIGÉ: Construction avec gestion des valeurs null
..._buildDrawerItemsFromSessionCache(),
// Déconnexion
const Divider(),
_buildLogoutItem(),
],
);
},
),
);
}
/// ✅ CORRIGÉ: Construction avec validation robuste des données
List<Widget> _buildDrawerItemsFromSessionCache() {
List<Widget> drawerItems = [];
// Vérifier si le cache est prêt
if (!userController.isCacheReady) {
return [
const Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
SizedBox(height: 8),
Text(
"Chargement du menu...",
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
];
}
// Récupérer les menus depuis le cache de session
final rawUserMenus = userController.getUserMenus();
// 🛡️ VALIDATION: Filtrer les menus valides
final validMenus = <Map<String, dynamic>>[];
final invalidMenus = <Map<String, dynamic>>[];
for (var menu in rawUserMenus) {
// Vérifier que les champs essentiels ne sont pas null
final name = menu['name'];
final route = menu['route'];
final id = menu['id'];
if (name != null && route != null && route.toString().isNotEmpty) {
validMenus.add({
'id': id,
'name': name.toString(),
'route': route.toString(),
});
} else {
invalidMenus.add(menu);
print("⚠️ Menu invalide ignoré dans CustomDrawer: id=$id, name='$name', route='$route'");
}
}
// Afficher les statistiques de validation
if (invalidMenus.isNotEmpty) {
print("📊 CustomDrawer: ${validMenus.length} menus valides, ${invalidMenus.length} invalides");
}
if (validMenus.isEmpty) {
return [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
"Aucun menu accessible",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
),
];
}
// 🔧 DÉDUPLICATION: Éliminer les doublons par route
final Map<String, Map<String, dynamic>> uniqueMenus = {};
for (var menu in validMenus) {
final route = menu['route'] as String;
uniqueMenus[route] = menu;
}
final deduplicatedMenus = uniqueMenus.values.toList();
if (deduplicatedMenus.length != validMenus.length) {
print("🔧 CustomDrawer: ${validMenus.length - deduplicatedMenus.length} doublons supprimés");
}
// Organiser les menus par catégories
final Map<String, List<Map<String, dynamic>>> categorizedMenus = {
'GESTION UTILISATEURS': [],
'GESTION PRODUITS': [],
'GESTION COMMANDES': [],
'RAPPORTS': [],
'ADMINISTRATION': [],
};
// Accueil toujours en premier
final accueilMenu = deduplicatedMenus.where((menu) => menu['route'] == '/accueil').firstOrNull;
if (accueilMenu != null) {
drawerItems.add(_buildDrawerItemFromMenu(accueilMenu));
}
// Catégoriser les autres menus avec validation supplémentaire
for (var menu in deduplicatedMenus) {
final route = menu['route'] as String;
// ✅ Validation supplémentaire avant categorisation
if (route.isEmpty) {
print("⚠️ Route vide ignorée: ${menu['name']}");
continue;
}
switch (route) {
case '/accueil':
// Déjà traité
break;
case '/ajouter-utilisateur':
case '/modifier-utilisateur':
case '/pointage':
categorizedMenus['GESTION UTILISATEURS']!.add(menu);
break;
case '/ajouter-produit':
case '/gestion-stock':
categorizedMenus['GESTION PRODUITS']!.add(menu);
break;
case '/nouvelle-commande':
case '/gerer-commandes':
categorizedMenus['GESTION COMMANDES']!.add(menu);
break;
case '/bilan':
case '/historique':
categorizedMenus['RAPPORTS']!.add(menu);
break;
case '/gerer-roles':
case '/points-de-vente':
categorizedMenus['ADMINISTRATION']!.add(menu);
break;
default:
// Menu non catégorisé
print("⚠️ Menu non catégorisé: $route");
break;
}
}
// Ajouter les catégories avec leurs menus
categorizedMenus.forEach((categoryName, menus) {
if (menus.isNotEmpty) {
drawerItems.add(_buildCategoryHeader(categoryName));
for (var menu in menus) {
drawerItems.add(_buildDrawerItemFromMenu(menu));
}
}
});
return drawerItems;
}
/// ✅ CORRIGÉ: Construction d'un item de menu avec validation
Widget _buildDrawerItemFromMenu(Map<String, dynamic> menu) {
// 🛡️ VALIDATION: Vérification des types avec gestion des null
final nameObj = menu['name'];
final routeObj = menu['route'];
if (nameObj == null || routeObj == null) {
print("⚠️ Menu invalide dans _buildDrawerItemFromMenu: name=$nameObj, route=$routeObj");
return const SizedBox.shrink();
}
final String name = nameObj.toString();
final String route = routeObj.toString();
if (name.isEmpty || route.isEmpty) {
print("⚠️ Menu avec valeurs vides: name='$name', route='$route'");
return const SizedBox.shrink();
}
// Mapping des routes vers les widgets et icônes
final Map<String, Map<String, dynamic>> routeMapping = {
'/accueil': {
'icon': Icons.home,
'color': Colors.blue,
'widget': DashboardPage(),
},
'/ajouter-utilisateur': {
'icon': Icons.person_add,
'color': Colors.green,
'widget': const RegistrationPage(),
},
'/modifier-utilisateur': {
'icon': Icons.supervised_user_circle,
'color': const Color.fromARGB(255, 4, 54, 95),
'widget': const ListUserPage(),
},
'/pointage': {
'icon': Icons.timer,
'color': const Color.fromARGB(255, 4, 54, 95),
'widget': null, // TODO: Implémenter
},
'/ajouter-produit': {
'icon': Icons.inventory,
'color': Colors.indigoAccent,
'widget': const ProductManagementPage(),
},
'/gestion-stock': {
'icon': Icons.storage,
'color': Colors.blueAccent,
'widget': const GestionStockPage(),
},
'/nouvelle-commande': {
'icon': Icons.add_shopping_cart,
'color': Colors.orange,
'widget': const NouvelleCommandePage(),
},
'/gerer-commandes': {
'icon': Icons.list_alt,
'color': Colors.deepPurple,
'widget': const GestionCommandesPage(),
},
'/bilan': {
'icon': Icons.bar_chart,
'color': Colors.teal,
'widget': DashboardPage(),
},
'/historique': {
'icon': Icons.history,
'color': Colors.blue,
'widget': const HistoriquePage(),
},
'/gerer-roles': {
'icon': Icons.admin_panel_settings,
'color': Colors.redAccent,
'widget': const RoleListPage(),
},
'/points-de-vente': {
'icon': Icons.store,
'color': Colors.blueGrey,
'widget': const AjoutPointDeVentePage(),
},
};
final routeData = routeMapping[route];
if (routeData == null) {
print("⚠️ Route non reconnue: '$route' pour le menu '$name'");
return ListTile(
leading: const Icon(Icons.help_outline, color: Colors.grey),
title: Text(name),
subtitle: Text("Route: $route", style: const TextStyle(fontSize: 10, color: Colors.grey)),
onTap: () {
Get.snackbar(
"Route non configurée",
"La route '$route' n'est pas encore configurée",
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange.shade100,
);
},
);
}
return ListTile(
leading: Icon(
routeData['icon'] as IconData,
color: routeData['color'] as Color,
),
title: Text(name),
trailing: const Icon(Icons.chevron_right, color: Colors.grey),
onTap: () {
final widget = routeData['widget'];
if (widget != null) {
Get.to(widget);
} else {
Get.snackbar(
"Non implémenté",
"Cette fonctionnalité sera bientôt disponible",
snackPosition: SnackPosition.BOTTOM,
);
}
},
);
}
/// Header de catégorie
Widget _buildCategoryHeader(String categoryName) {
return Padding(
padding: const EdgeInsets.only(left: 20, top: 15, bottom: 5),
child: Text(
categoryName,
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
);
}
/// Header utilisateur amélioré
Widget _buildUserHeader(UserController controller) {
return Container(
padding: const EdgeInsets.only(top: 50, left: 20, bottom: 20),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color.fromARGB(255, 4, 54, 95), Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Row(
children: [
const CircleAvatar(
radius: 30,
backgroundImage: AssetImage("assets/youmaz2.png"),
),
const SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.name.isNotEmpty
? controller.fullName
: 'Utilisateur',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
controller.role.isNotEmpty ? controller.role : 'Aucun rôle',
style: const TextStyle(
color: Colors.white70,
fontSize: 12,
),
),
if (controller.pointDeVenteDesignation.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
controller.pointDeVenteDesignation,
style: const TextStyle(
color: Colors.white60,
fontSize: 10,
),
),
],
// ✅ Indicateur de statut du cache
const SizedBox(height: 4),
Row(
children: [
Icon(
controller.isCacheReady ? Icons.check_circle : Icons.hourglass_empty,
color: controller.isCacheReady ? Colors.green : Colors.orange,
size: 12,
),
const SizedBox(width: 4),
Text(
controller.isCacheReady ? 'Menu prêt' : 'Chargement...',
style: const TextStyle(
color: Colors.white60,
fontSize: 10,
),
),
],
),
],
),
),
// ✅ Bouton de rafraîchissement pour les admins
if (controller.role == 'Super Admin' || controller.role == 'Admin') ...[
IconButton(
icon: const Icon(Icons.refresh, color: Colors.white70, size: 20),
onPressed: () async {
Get.snackbar(
"Cache",
"Rechargement des permissions...",
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 1),
);
await controller.refreshPermissions();
Get.back(); // Fermer le drawer
Get.snackbar(
"Cache",
"Permissions rechargées avec succès",
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
},
tooltip: "Recharger les permissions",
),
],
// 🔧 Bouton de debug (à supprimer en production)
if (controller.role == 'Super Admin') ...[
IconButton(
icon: const Icon(Icons.bug_report, color: Colors.white70, size: 18),
onPressed: () {
// Debug des menus
final menus = controller.getUserMenus();
String debugInfo = "MENUS DEBUG:\n";
for (var i = 0; i < menus.length; i++) {
final menu = menus[i];
debugInfo += "[$i] ID:${menu['id']}, Name:'${menu['name']}', Route:'${menu['route']}'\n";
}
Get.dialog(
AlertDialog(
title: const Text("Debug Menus"),
content: SingleChildScrollView(
child: Text(debugInfo, style: const TextStyle(fontSize: 12)),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text("Fermer"),
),
],
),
);
},
tooltip: "Debug menus",
),
],
],
),
);
}
/// Item de déconnexion
Widget _buildLogoutItem() {
return ListTile(
leading: const Icon(Icons.logout, color: Colors.red),
title: const Text("Déconnexion"),
onTap: () {
Get.dialog(
AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
contentPadding: EdgeInsets.zero,
content: Container(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
child: Column(
children: [
Icon(
Icons.logout_rounded,
size: 48,
color: Colors.orange.shade600,
),
const SizedBox(height: 16),
const Text(
"Déconnexion",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 12),
const Text(
"Êtes-vous sûr de vouloir vous déconnecter ?",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Colors.black87,
height: 1.4,
),
),
const SizedBox(height: 8),
Text(
"Vos permissions seront rechargées à la prochaine connexion.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
height: 1.3,
),
),
],
),
),
// Actions
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Get.back(),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
side: BorderSide(
color: Colors.grey.shade300,
width: 1.5,
),
),
child: const Text(
"Annuler",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
// ✅ IMPORTANT: Vider le cache de session lors de la déconnexion
await clearUserData();
Get.offAll(const LoginPage());
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade600,
foregroundColor: Colors.white,
elevation: 2,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
"Se déconnecter",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
],
),
),
),
barrierDismissible: true,
);
},
);
}
}