diff --git a/em2rp/lib/providers/users_provider.dart b/em2rp/lib/providers/users_provider.dart index 413ddac..90ef1a7 100644 --- a/em2rp/lib/providers/users_provider.dart +++ b/em2rp/lib/providers/users_provider.dart @@ -1,4 +1,3 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import '../models/user_model.dart'; import '../services/user_service.dart'; diff --git a/em2rp/lib/views/my_account_page.dart b/em2rp/lib/views/my_account_page.dart index 47315f6..024e63e 100644 --- a/em2rp/lib/views/my_account_page.dart +++ b/em2rp/lib/views/my_account_page.dart @@ -1,10 +1,9 @@ import 'package:em2rp/providers/local_user_provider.dart'; -import 'package:em2rp/providers/users_provider.dart'; -import 'package:em2rp/views/widgets/image/profile_picture.dart'; import 'package:em2rp/views/widgets/nav/main_drawer.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/views/widgets/inputs/styled_text_field.dart'; +import 'package:em2rp/views/widgets/image/profile_picture_selector.dart'; class MyAccountPage extends StatelessWidget { const MyAccountPage({super.key}); @@ -17,7 +16,6 @@ class MyAccountPage extends StatelessWidget { body: Consumer( builder: (context, userProvider, child) { final user = userProvider.currentUser; - final usersProvider = context.read(); if (user == null) { return const Center(child: CircularProgressIndicator()); @@ -34,18 +32,7 @@ class MyAccountPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - GestureDetector( - //onTap: () => userProvider.changeProfilePicture(); TODO - child: Stack( - alignment: Alignment.center, - children: [ - ProfilePictureWidget( - userId: user.uid, - radius: 80, - ), - ], - ), - ), + const ProfilePictureSelector(), Center( child: Card( elevation: 4.0, diff --git a/em2rp/lib/views/user_management_page.dart b/em2rp/lib/views/user_management_page.dart index 6452e70..8c422ea 100644 --- a/em2rp/lib/views/user_management_page.dart +++ b/em2rp/lib/views/user_management_page.dart @@ -1,66 +1,147 @@ -import 'package:em2rp/providers/local_user_provider.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'; -class UserManagementPage extends StatelessWidget { - const UserManagementPage({super.key}); +class UserManagementPage extends StatefulWidget { + const UserManagementPage({Key? key}) : super(key: key); + + @override + State createState() => _UserManagementPageState(); +} + +class _UserManagementPageState extends State { + @override + void initState() { + super.initState(); + Future.microtask( + () => Provider.of(context, listen: false).fetchUsers()); + } @override Widget build(BuildContext context) { - // final userViewModel = Provider.of(context); - final authProvider = Provider.of(context); - - if (authProvider.role != 'ADMIN') { - return Scaffold( - appBar: AppBar(title: const Text('Gestion des Utilisateurs')), - body: const Center( - child: Text('Accès non autorisé', - style: TextStyle(color: Colors.red))), - ); - } - return Scaffold( - appBar: AppBar(title: const Text('Gestion des Utilisateurs')), - // body: userViewModel.isLoading - // ? const Center(child: CircularProgressIndicator()) - // : ListView.builder( - // itemCount: userViewModel.users.length, - // itemBuilder: (context, index) { - // final user = userViewModel.users[index]; - // return ListTile( - // leading: CircleAvatar( - // backgroundImage: NetworkImage(user.profilePhotoUrl)), - // title: Text('${user.firstName} ${user.lastName}'), - // subtitle: Text(user.email), - // trailing: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // IconButton( - // icon: const Icon(Icons.edit), - // onPressed: () { - // // Afficher la pop-up d'édition - // }, - // ), - // IconButton( - // icon: const Icon(Icons.lock_reset), - // onPressed: () => - // userViewModel.resetPassword(user.email), - // ), - // IconButton( - // icon: const Icon(Icons.delete, color: Colors.red), - // onPressed: () => userViewModel.deleteUser(user.uid), - // ), - // ], - // ), - // ); - // }, - // ), - // floatingActionButton: FloatingActionButton( - // onPressed: () { - // // Ajouter un utilisateur - // }, - // child: const Icon(Icons.add), - // ), + 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é")); + } + + // Détermine le nombre de colonnes selon la largeur + final width = MediaQuery.of(context).size.width; + int crossAxisCount = 1; + if (width > 1200) + crossAxisCount = 4; + else if (width > 800) + crossAxisCount = 3; + else if (width > 600) crossAxisCount = 2; + + return Padding( + padding: const EdgeInsets.all(16), + child: GridView.builder( + itemCount: users.length, + // Dans GridView.builder + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: width > 1200 + ? 4 + : width > 800 + ? 3 + : width > 600 + ? 2 + : 1, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + // childAspectRatio fixé pour desktop ; mobile ignore car row layout + ), + + 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(); + final roleController = TextEditingController(); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Créer un utilisateur'), + content: Column( + mainAxisSize: MainAxisSize.min, + 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')), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Annuler'), + ), + TextButton( + onPressed: () { + final newUser = UserModel( + uid: '', + firstName: firstNameController.text, + lastName: lastNameController.text, + email: emailController.text, + phoneNumber: phoneController.text, + role: roleController.text, + profilePhotoUrl: '', + ); + Navigator.pop(context); + }, + child: const Text('Créer'), + ), + ], + ), ); } } diff --git a/em2rp/lib/views/widgets/image/profile_picture_selector.dart b/em2rp/lib/views/widgets/image/profile_picture_selector.dart new file mode 100644 index 0000000..f90c7a8 --- /dev/null +++ b/em2rp/lib/views/widgets/image/profile_picture_selector.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:provider/provider.dart'; +import 'package:em2rp/providers/local_user_provider.dart'; +import 'package:em2rp/views/widgets/image/profile_picture.dart'; + +class ProfilePictureSelector extends StatefulWidget { + const ProfilePictureSelector({super.key}); + + @override + State createState() => _ProfilePictureSelectorState(); +} + +class _ProfilePictureSelectorState extends State { + bool _isHovering = false; + + Future _pickAndUploadImage() async { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage(source: ImageSource.gallery); + if (image != null) { + // Envoie l'image au provider + await Provider.of(context, listen: false) + .changeProfilePicture(image); + } + } + + @override + Widget build(BuildContext context) { + final userProvider = Provider.of(context); + final String userId = userProvider.uid ?? ''; + + return MouseRegion( + onEnter: (_) => setState(() => _isHovering = true), + onExit: (_) => setState(() => _isHovering = false), + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: _pickAndUploadImage, + child: Stack( + alignment: Alignment.center, + children: [ + ProfilePictureWidget(userId: userId, radius: 80), + if (_isHovering) + Container( + width: 160, + height: 160, + decoration: BoxDecoration( + color: Colors.black54, + shape: BoxShape.circle, + ), + child: const Center( + child: Icon( + Icons.edit, + size: 36, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/em2rp/lib/views/widgets/user_management/edit_user_dialog.dart b/em2rp/lib/views/widgets/user_management/edit_user_dialog.dart new file mode 100644 index 0000000..6ace7e6 --- /dev/null +++ b/em2rp/lib/views/widgets/user_management/edit_user_dialog.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:em2rp/models/user_model.dart'; +import 'package:em2rp/providers/users_provider.dart'; + +class EditUserDialog extends StatefulWidget { + final UserModel user; + const EditUserDialog({required this.user, super.key}); + + @override + State createState() => _EditUserDialogState(); +} + +class _EditUserDialogState extends State { + late final TextEditingController firstNameController; + late final TextEditingController lastNameController; + late final TextEditingController emailController; + late final TextEditingController phoneController; + late final TextEditingController roleController; + + @override + void initState() { + super.initState(); + firstNameController = TextEditingController(text: widget.user.firstName); + lastNameController = TextEditingController(text: widget.user.lastName); + emailController = TextEditingController(text: widget.user.email); + phoneController = TextEditingController(text: widget.user.phoneNumber); + roleController = TextEditingController(text: widget.user.role); + } + + @override + void dispose() { + firstNameController.dispose(); + lastNameController.dispose(); + emailController.dispose(); + phoneController.dispose(); + roleController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Modifier utilisateur'), + content: SingleChildScrollView( + child: Column( + 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')), + ], + ), + ), + 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/views/widgets/user_management/user_card.dart b/em2rp/lib/views/widgets/user_management/user_card.dart new file mode 100644 index 0000000..7a65eba --- /dev/null +++ b/em2rp/lib/views/widgets/user_management/user_card.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:em2rp/models/user_model.dart'; +import 'package:em2rp/utils/colors.dart'; + +class UserCard extends StatelessWidget { + final UserModel user; + final VoidCallback onEdit; + final VoidCallback onDelete; + + // Hauteurs fixes selon le device + static const double _mobileHeight = 150; + static const double _desktopHeight = 280; + + const UserCard({ + Key? key, + required this.user, + required this.onEdit, + required this.onDelete, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final isMobile = width < 600; + final cardHeight = isMobile ? _mobileHeight : _desktopHeight; + + return SizedBox( + height: cardHeight, // Hauteur fixe + child: Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 3, + child: Padding( + padding: const EdgeInsets.all(12), + child: isMobile + ? _buildMobileRow(context) + : _buildDesktopColumn(context), + ), + ), + ); + } + + Widget _buildMobileRow(BuildContext context) { + return Row( + children: [ + _profileAvatar(48), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, // Centré verticalement + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("${user.firstName} ${user.lastName}", + style: Theme.of(context).textTheme.titleSmall), + Text(user.email, style: Theme.of(context).textTheme.bodySmall), + const SizedBox(height: 8), + Row( + children: [ + Flexible( + child: ElevatedButton.icon( + icon: const Icon(Icons.edit, size: 14), + label: const Text("Modifier"), + onPressed: onEdit, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.rouge, + foregroundColor: AppColors.blanc, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + textStyle: const TextStyle(fontSize: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6)), + ), + ), + ), + const SizedBox(width: 8), + Flexible( + child: ElevatedButton.icon( + icon: const Icon(Icons.delete, size: 14), + label: const Text("Supprimer"), + onPressed: onDelete, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.gris, + foregroundColor: AppColors.blanc, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + textStyle: const TextStyle(fontSize: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6)), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } + + Widget _buildDesktopColumn(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, // Écarte haut/bas + children: [ + _profileAvatar(80), + Column( + children: [ + Text("${user.firstName} ${user.lastName}", + style: Theme.of(context).textTheme.titleSmall), + const SizedBox(height: 4), + Text(user.email, style: Theme.of(context).textTheme.bodySmall), + const SizedBox(height: 4), + Text(user.role, + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: AppColors.gris)), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton.icon( + icon: const Icon(Icons.edit, size: 18), + label: const Text("Modifier"), + onPressed: onEdit, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.rouge, + foregroundColor: AppColors.blanc, + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + ), + ), + ElevatedButton.icon( + icon: const Icon(Icons.delete, size: 18), + label: const Text("Supprimer"), + onPressed: onDelete, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.gris, + foregroundColor: AppColors.blanc, + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + ), + ), + ], + ), + ], + ); + } + + Widget _profileAvatar(double size) { + return CircleAvatar( + radius: size / 2, + backgroundImage: user.profilePhotoUrl.isNotEmpty + ? NetworkImage(user.profilePhotoUrl) + : null, + backgroundColor: Colors.grey[200], + child: user.profilePhotoUrl.isEmpty + ? Icon(Icons.person, size: size * 0.6, color: AppColors.noir) + : null, + ); + } +}