import 'package:flutter/material.dart'; import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/role.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; class EditUserPage extends StatefulWidget { final Users user; const EditUserPage({super.key, required this.user}); @override _EditUserPageState createState() => _EditUserPageState(); } class _EditUserPageState extends State { late TextEditingController _nameController; late TextEditingController _lastNameController; late TextEditingController _emailController; late TextEditingController _usernameController; late TextEditingController _passwordController; List _roles = []; List> _pointsDeVente = []; Role? _selectedRole; Map? _selectedPointDeVente; bool _isLoading = false; bool _isLoadingData = true; @override void initState() { super.initState(); _nameController = TextEditingController(text: widget.user.name); _lastNameController = TextEditingController(text: widget.user.lastName); _emailController = TextEditingController(text: widget.user.email); _usernameController = TextEditingController(text: widget.user.username); _passwordController = TextEditingController(); _loadInitialData(); } Future _loadInitialData() async { try { // Charger les rôles final roles = await AppDatabase.instance.getRoles(); final currentRole = roles.firstWhere( (r) => r.id == widget.user.roleId, orElse: () => Role(id: widget.user.roleId, designation: widget.user.roleName ?? 'Inconnu'), ); // Charger les points de vente final pointsDeVente = await AppDatabase.instance.getPointsDeVente(); // Trouver le point de vente actuel de l'utilisateur Map? currentPointDeVente; if (widget.user.pointDeVenteId != null) { try { currentPointDeVente = pointsDeVente.firstWhere( (pv) => pv['id'] == widget.user.pointDeVenteId, ); } catch (e) { // Point de vente non trouvé, on garde null print('Point de vente ${widget.user.pointDeVenteId} non trouvé'); } } setState(() { _roles = roles; _selectedRole = currentRole; _pointsDeVente = pointsDeVente; _selectedPointDeVente = currentPointDeVente; _isLoadingData = false; }); } catch (e) { print('Erreur lors du chargement des données: $e'); setState(() { _isLoadingData = false; }); _showErrorDialog('Erreur', 'Impossible de charger les données nécessaires.'); } } @override void dispose() { _nameController.dispose(); _lastNameController.dispose(); _emailController.dispose(); _usernameController.dispose(); _passwordController.dispose(); super.dispose(); } // ✅ AMÉLIORÉ: Validation des champs avec messages plus précis bool _validateFields() { if (_nameController.text.trim().isEmpty) { _showErrorDialog('Champ manquant', 'Le prénom est requis.'); return false; } if (_lastNameController.text.trim().isEmpty) { _showErrorDialog('Champ manquant', 'Le nom de famille est requis.'); return false; } if (_emailController.text.trim().isEmpty) { _showErrorDialog('Champ manquant', 'L\'email est requis.'); return false; } if (_usernameController.text.trim().isEmpty) { _showErrorDialog('Champ manquant', 'Le nom d\'utilisateur est requis.'); return false; } if (_selectedRole == null) { _showErrorDialog('Champ manquant', 'Veuillez sélectionner un rôle.'); return false; } if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$') .hasMatch(_emailController.text.trim())) { _showErrorDialog('Email invalide', 'Veuillez saisir un email valide.'); return false; } if (_passwordController.text.isNotEmpty && _passwordController.text.length < 6) { _showErrorDialog('Mot de passe trop court', 'Le mot de passe doit contenir au minimum 6 caractères.'); return false; } if (_usernameController.text.trim().length < 3) { _showErrorDialog('Nom d\'utilisateur trop court', 'Le nom d\'utilisateur doit contenir au minimum 3 caractères.'); return false; } return true; } // ✅ CORRIGÉ: Méthode _updateUser avec validation et gestion d'erreurs améliorée Future _updateUser() async { if (!_validateFields() || _isLoading) return; setState(() { _isLoading = true; }); try { print("🔄 Début de la mise à jour utilisateur..."); // ✅ Créer l'objet utilisateur avec toutes les données final updatedUser = Users( id: widget.user.id, name: _nameController.text.trim(), lastName: _lastNameController.text.trim(), email: _emailController.text.trim(), username: _usernameController.text.trim(), password: _passwordController.text.isNotEmpty ? _passwordController.text : widget.user.password, roleId: _selectedRole!.id!, roleName: _selectedRole!.designation, pointDeVenteId: _selectedPointDeVente?['id'], ); print("📝 Données utilisateur à mettre à jour:"); print(" ID: ${updatedUser.id}"); print(" Nom: ${updatedUser.name} ${updatedUser.lastName}"); print(" Email: ${updatedUser.email}"); print(" Username: ${updatedUser.username}"); print(" Role ID: ${updatedUser.roleId}"); print(" Point de vente ID: ${updatedUser.pointDeVenteId}"); // ✅ Validation avant mise à jour final validationError = await AppDatabase.instance.validateUserUpdate(updatedUser); if (validationError != null) { if (mounted) { _showErrorDialog('Erreur de validation', validationError); } return; } // ✅ Vérifier que l'utilisateur existe final userExists = await AppDatabase.instance.userExists(widget.user.id!); if (!userExists) { if (mounted) { _showErrorDialog('Erreur', 'L\'utilisateur à modifier n\'existe plus dans la base de données.'); } return; } // ✅ Effectuer la mise à jour final affectedRows = await AppDatabase.instance.updateUser(updatedUser); if (affectedRows > 0) { print("✅ Utilisateur mis à jour avec succès!"); if (mounted) _showSuccessDialog(); } else { print("⚠️ Aucune ligne affectée lors de la mise à jour"); if (mounted) { _showErrorDialog('Information', 'Aucune modification n\'a été effectuée.'); } } } catch (e) { print('❌ Erreur de mise à jour: $e'); if (mounted) { String errorMessage = 'Une erreur est survenue lors de la mise à jour.'; // Messages d'erreur plus spécifiques if (e.toString().contains('Duplicate entry')) { if (e.toString().contains('email')) { errorMessage = 'Cet email est déjà utilisé par un autre utilisateur.'; } else if (e.toString().contains('username')) { errorMessage = 'Ce nom d\'utilisateur est déjà utilisé.'; } else { errorMessage = 'Ces informations sont déjà utilisées par un autre utilisateur.'; } } else if (e.toString().contains('Cannot add or update a child row')) { errorMessage = 'Le rôle ou le point de vente sélectionné n\'existe pas.'; } else if (e.toString().contains('Connection')) { errorMessage = 'Erreur de connexion à la base de données. Veuillez réessayer.'; } _showErrorDialog('Échec', errorMessage); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } } void _showSuccessDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: Row( children: const [ Icon(Icons.check_circle, color: Colors.green), SizedBox(width: 8), Text('Mise à jour réussie'), ], ), content: const Text('Les informations de l\'utilisateur ont été mises à jour avec succès.'), actions: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(); // Fermer le dialog Navigator.of(context).pop(); // Retourner à la page précédente }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, ), child: const Text('OK'), ) ], ), ); } void _showErrorDialog(String title, String message) { showDialog( context: context, builder: (context) => AlertDialog( title: Row( children: [ const Icon(Icons.error, color: Colors.red), const SizedBox(width: 8), Text(title), ], ), content: Text(message), actions: [ ElevatedButton( onPressed: () => Navigator.of(context).pop(), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('OK'), ) ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Modifier Utilisateur', style: TextStyle(color: Colors.white)), backgroundColor: const Color.fromARGB(255, 4, 54, 95), iconTheme: const IconThemeData(color: Colors.white), centerTitle: true, ), body: _isLoadingData ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Chargement des données...'), ], ), ) : SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ // En-tête Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.edit, size: 32, color: Colors.blue), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Modification d\'utilisateur', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blue, ), ), Text( 'ID: ${widget.user.id}', style: TextStyle( fontSize: 12, color: Colors.grey.shade600, ), ), ], ), ], ), ), const SizedBox(height: 24), // Informations personnelles _buildSectionTitle('Informations personnelles'), const SizedBox(height: 12), _buildTextField(_nameController, 'Prénom', Icons.person), const SizedBox(height: 12), _buildTextField(_lastNameController, 'Nom', Icons.person_outline), const SizedBox(height: 12), _buildTextField(_emailController, 'Email', Icons.email, keyboardType: TextInputType.emailAddress), const SizedBox(height: 24), // Informations de connexion _buildSectionTitle('Informations de connexion'), const SizedBox(height: 12), _buildTextField(_usernameController, 'Nom d\'utilisateur', Icons.account_circle), const SizedBox(height: 12), _buildTextField( _passwordController, 'Nouveau mot de passe (optionnel)', Icons.lock, obscureText: true, ), const SizedBox(height: 24), // Permissions et affectation _buildSectionTitle('Permissions et affectation'), const SizedBox(height: 12), _buildRoleDropdown(), const SizedBox(height: 12), _buildPointDeVenteDropdown(), const SizedBox(height: 32), // Boutons d'action Row( children: [ Expanded( child: OutlinedButton( onPressed: _isLoading ? null : () => Navigator.of(context).pop(), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), side: const BorderSide(color: Colors.grey), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text('Annuler'), ), ), const SizedBox(width: 16), Expanded( flex: 2, child: ElevatedButton( onPressed: _isLoading ? null : _updateUser, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0015B7), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : const Text( 'Mettre à jour', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), ), ), ], ) ], ), ), ), ), ); } Widget _buildSectionTitle(String title) { return Container( width: double.infinity, padding: const EdgeInsets.only(bottom: 8), decoration: const BoxDecoration( border: Border( bottom: BorderSide(color: Colors.grey, width: 0.5), ), ), child: Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Color.fromARGB(255, 4, 54, 95), ), ), ); } Widget _buildTextField( TextEditingController controller, String label, IconData icon, { TextInputType keyboardType = TextInputType.text, bool obscureText = false, }) { return TextField( controller: controller, decoration: InputDecoration( labelText: label, prefixIcon: Icon(icon, color: Colors.grey.shade600), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFF0015B7), width: 2), ), filled: true, fillColor: Colors.grey.shade50, ), keyboardType: keyboardType, obscureText: obscureText, ); } Widget _buildRoleDropdown() { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), color: Colors.grey.shade50, ), child: DropdownButtonHideUnderline( child: DropdownButton( value: _selectedRole, isExpanded: true, hint: const Text('Sélectionner un rôle'), onChanged: _isLoading ? null : (Role? newValue) { setState(() { _selectedRole = newValue; }); }, items: _roles.map((role) { return DropdownMenuItem( value: role, child: Row( children: [ Icon(Icons.badge, size: 20, color: Colors.grey.shade600), const SizedBox(width: 8), Text(role.designation), ], ), ); }).toList(), ), ), ); } Widget _buildPointDeVenteDropdown() { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), color: Colors.grey.shade50, ), child: DropdownButtonHideUnderline( child: DropdownButton?>( value: _selectedPointDeVente, isExpanded: true, hint: const Text('Sélectionner un point de vente (optionnel)'), onChanged: _isLoading ? null : (Map? newValue) { setState(() { _selectedPointDeVente = newValue; }); }, items: [ // Option "Aucun point de vente" const DropdownMenuItem?>( value: null, child: Row( children: [ Icon(Icons.not_interested, size: 20, color: Colors.grey), SizedBox(width: 8), Text('Aucun point de vente'), ], ), ), // Points de vente disponibles ..._pointsDeVente.map((pointDeVente) { return DropdownMenuItem>( value: pointDeVente, child: Row( children: [ Icon(Icons.store, size: 20, color: Colors.grey.shade600), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(pointDeVente['nom'] ?? 'N/A'), if (pointDeVente['code'] != null) Text( 'Code: ${pointDeVente['code']}', style: TextStyle( fontSize: 12, color: Colors.grey.shade500, ), ), ], ), ), ], ), ); }).toList(), ], ), ), ); } }