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
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,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|