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.
 
 
 
 
 
 

600 lines
21 KiB

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<EditUserPage> {
late TextEditingController _nameController;
late TextEditingController _lastNameController;
late TextEditingController _emailController;
late TextEditingController _usernameController;
late TextEditingController _passwordController;
List<Role> _roles = [];
List<Map<String, dynamic>> _pointsDeVente = [];
Role? _selectedRole;
Map<String, dynamic>? _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<void> _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<String, dynamic>? 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<void> _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<Role>(
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<Role>(
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<Map<String, dynamic>?>(
value: _selectedPointDeVente,
isExpanded: true,
hint: const Text('Sélectionner un point de vente (optionnel)'),
onChanged: _isLoading
? null
: (Map<String, dynamic>? newValue) {
setState(() {
_selectedPointDeVente = newValue;
});
},
items: [
// Option "Aucun point de vente"
const DropdownMenuItem<Map<String, dynamic>?>(
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<Map<String, dynamic>>(
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(),
],
),
),
);
}
}