diff --git a/em2rp/lib/models/role_model.dart b/em2rp/lib/models/role_model.dart new file mode 100644 index 0000000..8963f72 --- /dev/null +++ b/em2rp/lib/models/role_model.dart @@ -0,0 +1,102 @@ +import 'package:flutter/foundation.dart'; + +enum Permission { + // Permissions liées aux prestations + viewAllEvents, // Voir toutes les prestations + viewAssignedEvents, // Voir uniquement les prestations assignées + editEvents, // Modifier les prestations + deleteEvents, // Supprimer les prestations + assignCrew, // Assigner des membres d'équipe aux prestations + + // Permissions liées aux finances + viewPrices, // Voir les prix + editPrices, // Modifier les prix + viewQuotes, // Voir les devis + createQuotes, // Créer des devis + editQuotes, // Modifier les devis + viewInvoices, // Voir les factures + createInvoices, // Créer des factures + editInvoices, // Modifier les factures + + // Permissions liées aux utilisateurs + viewUsers, // Voir les utilisateurs + editUsers, // Modifier les utilisateurs + deleteUsers, // Supprimer les utilisateurs + + // Permissions liées aux clients + viewClients, // Voir les clients + editClients, // Modifier les clients + deleteClients, // Supprimer les clients +} + +class Role { + final String name; + final Set permissions; + + const Role({ + required this.name, + required this.permissions, + }); + + bool hasPermission(Permission permission) => permissions.contains(permission); + + bool hasAllPermissions(List requiredPermissions) { + return requiredPermissions + .every((permission) => permissions.contains(permission)); + } + + bool hasAnyPermission(List requiredPermissions) { + return requiredPermissions + .any((permission) => permissions.contains(permission)); + } +} + +class Roles { + static const admin = Role( + name: 'ADMIN', + permissions: { + // Toutes les permissions pour l'administrateur + Permission.viewAllEvents, + Permission.viewAssignedEvents, + Permission.editEvents, + Permission.deleteEvents, + Permission.assignCrew, + Permission.viewPrices, + Permission.editPrices, + Permission.viewQuotes, + Permission.createQuotes, + Permission.editQuotes, + Permission.viewInvoices, + Permission.createInvoices, + Permission.editInvoices, + Permission.viewUsers, + Permission.editUsers, + Permission.deleteUsers, + Permission.viewClients, + Permission.editClients, + Permission.deleteClients, + }, + ); + + static const crew = Role( + name: 'CREW', + permissions: { + // Permissions limitées pour l'équipe + Permission.viewAssignedEvents, + Permission.viewClients, + }, + ); + + static Role fromString(String roleName) { + switch (roleName.toUpperCase()) { + case 'ADMIN': + return admin; + case 'CREW': + return crew; + default: + return crew; // Par défaut, on donne les permissions minimales + } + } + + static List values = [admin, crew]; +} diff --git a/em2rp/lib/views/user_management_page.dart b/em2rp/lib/views/user_management_page.dart index 464e936..dc33162 100644 --- a/em2rp/lib/views/user_management_page.dart +++ b/em2rp/lib/views/user_management_page.dart @@ -6,6 +6,8 @@ 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/widgets/permission_gate.dart'; +import 'package:em2rp/models/role_model.dart'; class UserManagementPage extends StatefulWidget { const UserManagementPage({Key? key}) : super(key: key); @@ -24,66 +26,79 @@ class _UserManagementPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Gestion des utilisateurs'), - backgroundColor: AppColors.rouge, + return PermissionGate( + requiredPermissions: [Permission.viewUsers], + fallback: Scaffold( + appBar: AppBar( + title: const Text('Accès refusé'), + backgroundColor: AppColors.rouge, + ), + body: const Center( + child: Text( + 'Vous n\'avez pas les permissions nécessaires pour accéder à cette page.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ), ), - drawer: const MainDrawer(currentPage: '/account_management'), - body: Consumer( - 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é")); - } + child: Scaffold( + appBar: AppBar( + title: const Text('Gestion des utilisateurs'), + backgroundColor: AppColors.rouge, + ), + drawer: const MainDrawer(currentPage: '/account_management'), + body: Consumer( + 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; + final width = MediaQuery.of(context).size.width; + int crossAxisCount; - // Ajustement du nombre de colonnes selon la taille d'écran - if (width > 1200) { - crossAxisCount = 4; - } else if (width > 800) { - crossAxisCount = 3; - } else if (width > 600) { - crossAxisCount = 2; - } else { - crossAxisCount = 1; - } + 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, // Augmenté de 170 à 180 pour le desktop + 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), + ); + }, ), - 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), + ); + }, + ), + floatingActionButton: FloatingActionButton( + backgroundColor: AppColors.rouge, + child: const Icon(Icons.add, color: AppColors.blanc), + onPressed: () => _showCreateUserDialog(context), + ), ), ); } diff --git a/em2rp/lib/views/widgets/nav/main_drawer.dart b/em2rp/lib/views/widgets/nav/main_drawer.dart index 7927049..c4d1e3b 100644 --- a/em2rp/lib/views/widgets/nav/main_drawer.dart +++ b/em2rp/lib/views/widgets/nav/main_drawer.dart @@ -6,11 +6,16 @@ import 'package:em2rp/views/user_management_page.dart'; import 'package:flutter/material.dart'; import 'package:em2rp/views/widgets/image/profile_picture.dart'; import 'package:provider/provider.dart'; +import 'package:em2rp/widgets/permission_gate.dart'; +import 'package:em2rp/models/role_model.dart'; class MainDrawer extends StatelessWidget { final String currentPage; - const MainDrawer({super.key, required this.currentPage}); + const MainDrawer({ + Key? key, + required this.currentPage, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -110,19 +115,23 @@ class MainDrawer extends StatelessWidget { ); }, ), - ListTile( - leading: const Icon(Icons.group), - title: const Text('Gestion des Utilisateurs'), - selected: currentPage == '/user_management', - selectedColor: AppColors.rouge, - onTap: () { - Navigator.pop(context); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const UserManagementPage()), - ); - }, + PermissionGate( + requiredPermissions: [Permission.viewUsers], + child: ListTile( + leading: const Icon(Icons.group), + title: const Text('Gestion des Utilisateurs'), + selected: currentPage == '/user_management', + selectedColor: AppColors.rouge, + onTap: () { + Navigator.pop(context); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const UserManagementPage()), + ); + }, + ), ), ], ), diff --git a/em2rp/lib/views/widgets/user_management/edit_user_dialog.dart b/em2rp/lib/views/widgets/user_management/edit_user_dialog.dart index 6ace7e6..da8aea1 100644 --- a/em2rp/lib/views/widgets/user_management/edit_user_dialog.dart +++ b/em2rp/lib/views/widgets/user_management/edit_user_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/models/user_model.dart'; import 'package:em2rp/providers/users_provider.dart'; +import 'package:em2rp/utils/colors.dart'; class EditUserDialog extends StatefulWidget { final UserModel user; @@ -16,7 +17,9 @@ class _EditUserDialogState extends State { late final TextEditingController lastNameController; late final TextEditingController emailController; late final TextEditingController phoneController; - late final TextEditingController roleController; + String selectedRole = ''; + + static const List roles = ['ADMIN', 'CREW']; @override void initState() { @@ -25,7 +28,7 @@ class _EditUserDialogState extends State { lastNameController = TextEditingController(text: widget.user.lastName); emailController = TextEditingController(text: widget.user.email); phoneController = TextEditingController(text: widget.user.phoneNumber); - roleController = TextEditingController(text: widget.user.role); + selectedRole = widget.user.role.isEmpty ? roles.first : widget.user.role; } @override @@ -34,56 +37,148 @@ class _EditUserDialogState extends State { lastNameController.dispose(); emailController.dispose(); phoneController.dispose(); - roleController.dispose(); super.dispose(); } + 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), + ); + } + @override Widget build(BuildContext context) { - return AlertDialog( - title: const Text('Modifier utilisateur'), - content: SingleChildScrollView( + 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: [ - TextField( - controller: firstNameController, - decoration: const InputDecoration(labelText: 'Prénom')), - TextField( - controller: lastNameController, - decoration: const InputDecoration(labelText: 'Nom')), - TextField( - controller: emailController, - decoration: const InputDecoration(labelText: 'Email')), - TextField( - controller: phoneController, - decoration: const InputDecoration(labelText: 'Téléphone')), - TextField( - controller: roleController, - decoration: const InputDecoration(labelText: 'Rôle')), + Row( + children: [ + const Icon(Icons.edit, color: AppColors.rouge), + const SizedBox(width: 12), + Text( + 'Modifier 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), + DropdownButtonFormField( + value: selectedRole, + decoration: _buildInputDecoration( + 'Rôle', Icons.admin_panel_settings_outlined), + items: roles.map((String role) { + return DropdownMenuItem( + value: role, + child: Text(role), + ); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + setState(() { + selectedRole = 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: () { + final updatedUser = widget.user.copyWith( + firstName: firstNameController.text, + lastName: lastNameController.text, + email: emailController.text, + phoneNumber: phoneController.text, + role: selectedRole, + ); + Provider.of(context, listen: false) + .updateUser(updatedUser); + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.rouge, + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text( + 'Enregistrer', + style: TextStyle(color: AppColors.blanc), + ), + ), + ], + ), ], ), ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Annuler'), - ), - ElevatedButton( - onPressed: () { - final updatedUser = widget.user.copyWith( - firstName: firstNameController.text, - lastName: lastNameController.text, - email: emailController.text, - phoneNumber: phoneController.text, - role: roleController.text, - ); - Provider.of(context, listen: false) - .updateUser(updatedUser); - Navigator.pop(context); - }, - child: const Text('Enregistrer'), - ), - ], ); } } diff --git a/em2rp/lib/widgets/permission_gate.dart b/em2rp/lib/widgets/permission_gate.dart new file mode 100644 index 0000000..970a181 --- /dev/null +++ b/em2rp/lib/widgets/permission_gate.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:em2rp/models/role_model.dart'; +import 'package:em2rp/providers/local_user_provider.dart'; + +class PermissionGate extends StatelessWidget { + final Widget child; + final List requiredPermissions; + final bool requireAll; + final Widget? fallback; + + const PermissionGate({ + super.key, + required this.child, + required this.requiredPermissions, + this.requireAll = true, + this.fallback, + }); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, localUserProvider, _) { + final currentUser = localUserProvider.currentUser; + if (currentUser == null) { + return fallback ?? const SizedBox.shrink(); + } + + final userRole = Roles.fromString(currentUser.role); + final hasPermission = requireAll + ? userRole.hasAllPermissions(requiredPermissions) + : userRole.hasAnyPermission(requiredPermissions); + + if (hasPermission) { + return child; + } + + return fallback ?? const SizedBox.shrink(); + }, + ); + } +}