307 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:em2rp/views/widgets/nav/main_drawer.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:provider/provider.dart';
 | |
| import 'package:em2rp/providers/users_provider.dart';
 | |
| import 'package:em2rp/models/user_model.dart';
 | |
| import 'package:em2rp/views/widgets/user_management/user_card.dart';
 | |
| import 'package:em2rp/views/widgets/user_management/edit_user_dialog.dart';
 | |
| import 'package:em2rp/utils/colors.dart';
 | |
| import 'package:em2rp/utils/permission_gate.dart';
 | |
| import 'package:em2rp/models/role_model.dart';
 | |
| import 'package:em2rp/views/widgets/nav/custom_app_bar.dart';
 | |
| import 'package:cloud_firestore/cloud_firestore.dart';
 | |
| 
 | |
| class UserManagementPage extends StatefulWidget {
 | |
|   const UserManagementPage({super.key});
 | |
| 
 | |
|   @override
 | |
|   State<UserManagementPage> createState() => _UserManagementPageState();
 | |
| }
 | |
| 
 | |
| class _UserManagementPageState extends State<UserManagementPage> {
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|     WidgetsBinding.instance.addPostFrameCallback((_) {
 | |
|       if (mounted) {
 | |
|         Provider.of<UsersProvider>(context, listen: false).fetchUsers();
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return PermissionGate(
 | |
|       requiredPermissions: const ['view_all_users'],
 | |
|       fallback: const Scaffold(
 | |
|         appBar: CustomAppBar(
 | |
|           title: 'Accès refusé',
 | |
|         ),
 | |
|         body: Center(
 | |
|           child: Text(
 | |
|             'Vous n\'avez pas les permissions nécessaires pour accéder à cette page.',
 | |
|             textAlign: TextAlign.center,
 | |
|             style: TextStyle(fontSize: 16),
 | |
|           ),
 | |
|         ),
 | |
|       ),
 | |
|       child: Scaffold(
 | |
|         appBar: const CustomAppBar(
 | |
|           title: 'Gestion des utilisateurs',
 | |
|         ),
 | |
|         drawer: const MainDrawer(currentPage: '/account_management'),
 | |
|         body: Consumer<UsersProvider>(
 | |
|           builder: (context, usersProvider, child) {
 | |
|             if (usersProvider.isLoading) {
 | |
|               return const Center(child: CircularProgressIndicator());
 | |
|             }
 | |
|             final users = usersProvider.users;
 | |
|             if (users.isEmpty) {
 | |
|               return const Center(child: Text("Aucun utilisateur trouvé"));
 | |
|             }
 | |
| 
 | |
|             final width = MediaQuery.of(context).size.width;
 | |
|             int crossAxisCount;
 | |
| 
 | |
|             if (width > 1200) {
 | |
|               crossAxisCount = 4;
 | |
|             } else if (width > 800) {
 | |
|               crossAxisCount = 3;
 | |
|             } else if (width > 600) {
 | |
|               crossAxisCount = 2;
 | |
|             } else {
 | |
|               crossAxisCount = 1;
 | |
|             }
 | |
| 
 | |
|             return Padding(
 | |
|               padding: const EdgeInsets.all(16),
 | |
|               child: GridView.builder(
 | |
|                 itemCount: users.length,
 | |
|                 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 | |
|                   crossAxisCount: crossAxisCount,
 | |
|                   crossAxisSpacing: 16,
 | |
|                   mainAxisSpacing: 16,
 | |
|                   mainAxisExtent: width < 600 ? 80 : 180,
 | |
|                 ),
 | |
|                 itemBuilder: (context, i) {
 | |
|                   final user = users[i];
 | |
|                   return UserCard(
 | |
|                     user: user,
 | |
|                     onEdit: () => showDialog(
 | |
|                         context: context,
 | |
|                         builder: (_) => EditUserDialog(user: user)),
 | |
|                     onDelete: () => usersProvider.deleteUser(user.uid),
 | |
|                   );
 | |
|                 },
 | |
|               ),
 | |
|             );
 | |
|           },
 | |
|         ),
 | |
|         floatingActionButton: FloatingActionButton(
 | |
|           backgroundColor: AppColors.rouge,
 | |
|           child: const Icon(Icons.add, color: AppColors.blanc),
 | |
|           onPressed: () => _showCreateUserDialog(context),
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   void _showCreateUserDialog(BuildContext context) {
 | |
|     final firstNameController = TextEditingController();
 | |
|     final lastNameController = TextEditingController();
 | |
|     final emailController = TextEditingController();
 | |
|     final phoneController = TextEditingController();
 | |
|     String? selectedRoleId;
 | |
|     List<RoleModel> availableRoles = [];
 | |
|     bool isLoadingRoles = true;
 | |
| 
 | |
|     Future<void> loadRoles() async {
 | |
|       final snapshot =
 | |
|           await FirebaseFirestore.instance.collection('roles').get();
 | |
|       availableRoles = snapshot.docs
 | |
|           .map((doc) => RoleModel.fromMap(doc.data(), doc.id))
 | |
|           .toList();
 | |
|       selectedRoleId =
 | |
|           availableRoles.isNotEmpty ? availableRoles.first.id : null;
 | |
|       isLoadingRoles = false;
 | |
|     }
 | |
| 
 | |
|     InputDecoration buildInputDecoration(String label, IconData icon) {
 | |
|       return InputDecoration(
 | |
|         labelText: label,
 | |
|         prefixIcon: Icon(icon, color: AppColors.rouge),
 | |
|         border: OutlineInputBorder(
 | |
|           borderRadius: BorderRadius.circular(8),
 | |
|         ),
 | |
|         focusedBorder: OutlineInputBorder(
 | |
|           borderRadius: BorderRadius.circular(8),
 | |
|           borderSide: const BorderSide(color: AppColors.rouge, width: 2),
 | |
|         ),
 | |
|         contentPadding:
 | |
|             const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     showDialog(
 | |
|       context: context,
 | |
|       builder: (context) => FutureBuilder(
 | |
|         future: loadRoles(),
 | |
|         builder: (context, snapshot) {
 | |
|           return Dialog(
 | |
|             shape: RoundedRectangleBorder(
 | |
|               borderRadius: BorderRadius.circular(16),
 | |
|             ),
 | |
|             child: Container(
 | |
|               width: 400,
 | |
|               padding: const EdgeInsets.all(24),
 | |
|               child: Column(
 | |
|                 mainAxisSize: MainAxisSize.min,
 | |
|                 crossAxisAlignment: CrossAxisAlignment.stretch,
 | |
|                 children: [
 | |
|                   Row(
 | |
|                     children: [
 | |
|                       const Icon(Icons.person_add, color: AppColors.rouge),
 | |
|                       const SizedBox(width: 12),
 | |
|                       Text(
 | |
|                         'Nouvel utilisateur',
 | |
|                         style: Theme.of(context).textTheme.titleLarge?.copyWith(
 | |
|                               color: AppColors.noir,
 | |
|                               fontWeight: FontWeight.bold,
 | |
|                             ),
 | |
|                       ),
 | |
|                     ],
 | |
|                   ),
 | |
|                   const SizedBox(height: 24),
 | |
|                   SingleChildScrollView(
 | |
|                     child: Column(
 | |
|                       mainAxisSize: MainAxisSize.min,
 | |
|                       children: [
 | |
|                         TextField(
 | |
|                           controller: firstNameController,
 | |
|                           decoration: buildInputDecoration(
 | |
|                               'Prénom', Icons.person_outline),
 | |
|                         ),
 | |
|                         const SizedBox(height: 16),
 | |
|                         TextField(
 | |
|                           controller: lastNameController,
 | |
|                           decoration: buildInputDecoration('Nom', Icons.person),
 | |
|                         ),
 | |
|                         const SizedBox(height: 16),
 | |
|                         TextField(
 | |
|                           controller: emailController,
 | |
|                           decoration: buildInputDecoration(
 | |
|                               'Email', Icons.email_outlined),
 | |
|                           keyboardType: TextInputType.emailAddress,
 | |
|                         ),
 | |
|                         const SizedBox(height: 16),
 | |
|                         TextField(
 | |
|                           controller: phoneController,
 | |
|                           decoration: buildInputDecoration(
 | |
|                               'Téléphone', Icons.phone_outlined),
 | |
|                           keyboardType: TextInputType.phone,
 | |
|                         ),
 | |
|                         const SizedBox(height: 16),
 | |
|                         isLoadingRoles
 | |
|                             ? const CircularProgressIndicator()
 | |
|                             : DropdownButtonFormField<String>(
 | |
|                                 initialValue: selectedRoleId,
 | |
|                                 decoration: buildInputDecoration('Rôle',
 | |
|                                     Icons.admin_panel_settings_outlined),
 | |
|                                 items: availableRoles.map((role) {
 | |
|                                   return DropdownMenuItem<String>(
 | |
|                                     value: role.id,
 | |
|                                     child: Text(role.name),
 | |
|                                   );
 | |
|                                 }).toList(),
 | |
|                                 onChanged: (String? newValue) {
 | |
|                                   if (newValue != null) {
 | |
|                                     selectedRoleId = newValue;
 | |
|                                   }
 | |
|                                 },
 | |
|                               ),
 | |
|                       ],
 | |
|                     ),
 | |
|                   ),
 | |
|                   const SizedBox(height: 24),
 | |
|                   Row(
 | |
|                     mainAxisAlignment: MainAxisAlignment.end,
 | |
|                     children: [
 | |
|                       TextButton(
 | |
|                         onPressed: () => Navigator.pop(context),
 | |
|                         style: TextButton.styleFrom(
 | |
|                           padding: const EdgeInsets.symmetric(
 | |
|                               horizontal: 16, vertical: 12),
 | |
|                         ),
 | |
|                         child: const Text(
 | |
|                           'Annuler',
 | |
|                           style: TextStyle(color: AppColors.gris),
 | |
|                         ),
 | |
|                       ),
 | |
|                       const SizedBox(width: 8),
 | |
|                       ElevatedButton(
 | |
|                         onPressed: () async {
 | |
|                           if (emailController.text.isEmpty ||
 | |
|                               firstNameController.text.isEmpty ||
 | |
|                               lastNameController.text.isEmpty ||
 | |
|                               selectedRoleId == null) {
 | |
|                             ScaffoldMessenger.of(context).showSnackBar(
 | |
|                               const SnackBar(
 | |
|                                 content: Text(
 | |
|                                     'Veuillez remplir tous les champs obligatoires'),
 | |
|                                 backgroundColor: Colors.red,
 | |
|                               ),
 | |
|                             );
 | |
|                             return;
 | |
|                           }
 | |
|                           try {
 | |
|                             final newUser = UserModel(
 | |
|                               uid: '', // Sera généré par Firebase
 | |
|                               firstName: firstNameController.text,
 | |
|                               lastName: lastNameController.text,
 | |
|                               email: emailController.text,
 | |
|                               phoneNumber: phoneController.text,
 | |
|                               role: selectedRoleId!,
 | |
|                               profilePhotoUrl: '',
 | |
|                             );
 | |
|                             await Provider.of<UsersProvider>(context,
 | |
|                                     listen: false)
 | |
|                                 .createUserWithEmailInvite(context, newUser,
 | |
|                                     roleId: selectedRoleId);
 | |
|                             Navigator.pop(context);
 | |
|                           } catch (e) {
 | |
|                             if (context.mounted) {
 | |
|                               ScaffoldMessenger.of(context).showSnackBar(
 | |
|                                 SnackBar(
 | |
|                                   content: Text(
 | |
|                                       'Erreur lors de la création: ${e.toString()}'),
 | |
|                                   backgroundColor: Colors.red,
 | |
|                                 ),
 | |
|                               );
 | |
|                             }
 | |
|                           }
 | |
|                         },
 | |
|                         style: ElevatedButton.styleFrom(
 | |
|                           backgroundColor: AppColors.rouge,
 | |
|                           padding: const EdgeInsets.symmetric(
 | |
|                               horizontal: 24, vertical: 12),
 | |
|                           shape: RoundedRectangleBorder(
 | |
|                             borderRadius: BorderRadius.circular(8),
 | |
|                           ),
 | |
|                         ),
 | |
|                         child: const Text(
 | |
|                           'Inviter',
 | |
|                           style: TextStyle(color: AppColors.blanc),
 | |
|                         ),
 | |
|                       ),
 | |
|                     ],
 | |
|                   ),
 | |
|                 ],
 | |
|               ),
 | |
|             ),
 | |
|           );
 | |
|         },
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 | 
