membre add
This commit is contained in:
@@ -8,13 +8,17 @@ class UserForm extends StatefulWidget {
|
||||
final UserModel? user;
|
||||
final Function(UserModel)? onSubmit;
|
||||
final bool readOnly;
|
||||
final bool allowUsernameEdit;
|
||||
final bool allowSectNameEdit;
|
||||
|
||||
const UserForm({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.user,
|
||||
this.onSubmit,
|
||||
this.readOnly = false,
|
||||
}) : super(key: key);
|
||||
this.allowUsernameEdit = false,
|
||||
this.allowSectNameEdit = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<UserForm> createState() => _UserFormState();
|
||||
@@ -27,6 +31,7 @@ class _UserFormState extends State<UserForm> {
|
||||
late final TextEditingController _usernameController;
|
||||
late final TextEditingController _firstNameController;
|
||||
late final TextEditingController _nameController;
|
||||
late final TextEditingController _sectNameController;
|
||||
late final TextEditingController _phoneController;
|
||||
late final TextEditingController _mobileController;
|
||||
late final TextEditingController _emailController;
|
||||
@@ -47,6 +52,7 @@ class _UserFormState extends State<UserForm> {
|
||||
_usernameController = TextEditingController(text: user?.username ?? '');
|
||||
_firstNameController = TextEditingController(text: user?.firstName ?? '');
|
||||
_nameController = TextEditingController(text: user?.name ?? '');
|
||||
_sectNameController = TextEditingController(text: user?.sectName ?? '');
|
||||
_phoneController = TextEditingController(text: user?.phone ?? '');
|
||||
_mobileController = TextEditingController(text: user?.mobile ?? '');
|
||||
_emailController = TextEditingController(text: user?.email ?? '');
|
||||
@@ -54,15 +60,9 @@ class _UserFormState extends State<UserForm> {
|
||||
_dateNaissance = user?.dateNaissance;
|
||||
_dateEmbauche = user?.dateEmbauche;
|
||||
|
||||
_dateNaissanceController = TextEditingController(
|
||||
text: _dateNaissance != null
|
||||
? DateFormat('dd/MM/yyyy').format(_dateNaissance!)
|
||||
: '');
|
||||
_dateNaissanceController = TextEditingController(text: _dateNaissance != null ? DateFormat('dd/MM/yyyy').format(_dateNaissance!) : '');
|
||||
|
||||
_dateEmbaucheController = TextEditingController(
|
||||
text: _dateEmbauche != null
|
||||
? DateFormat('dd/MM/yyyy').format(_dateEmbauche!)
|
||||
: '');
|
||||
_dateEmbaucheController = TextEditingController(text: _dateEmbauche != null ? DateFormat('dd/MM/yyyy').format(_dateEmbauche!) : '');
|
||||
|
||||
_fkTitre = user?.fkTitre ?? 1;
|
||||
}
|
||||
@@ -72,6 +72,7 @@ class _UserFormState extends State<UserForm> {
|
||||
_usernameController.dispose();
|
||||
_firstNameController.dispose();
|
||||
_nameController.dispose();
|
||||
_sectNameController.dispose();
|
||||
_phoneController.dispose();
|
||||
_mobileController.dispose();
|
||||
_emailController.dispose();
|
||||
@@ -80,6 +81,19 @@ class _UserFormState extends State<UserForm> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Validation conditionnelle pour name/sectName
|
||||
String? _validateNameOrSectName(String? value, bool isNameField) {
|
||||
final nameValue = _nameController.text.trim();
|
||||
final sectNameValue = _sectNameController.text.trim();
|
||||
|
||||
// Si les deux sont vides
|
||||
if (nameValue.isEmpty && sectNameValue.isEmpty) {
|
||||
return isNameField ? "Veuillez renseigner soit le nom soit le nom de tournée" : "Veuillez renseigner soit le nom de tournée soit le nom";
|
||||
}
|
||||
|
||||
return null; // Validation OK si au moins un des deux est rempli
|
||||
}
|
||||
|
||||
// Méthode simplifiée pour sélectionner une date
|
||||
void _selectDate(BuildContext context, bool isDateNaissance) {
|
||||
// Utiliser un bloc try-catch pour capturer toutes les erreurs possibles
|
||||
@@ -98,12 +112,10 @@ class _UserFormState extends State<UserForm> {
|
||||
// Mettre à jour la date et le texte du contrôleur
|
||||
if (isDateNaissance) {
|
||||
_dateNaissance = picked;
|
||||
_dateNaissanceController.text =
|
||||
DateFormat('dd/MM/yyyy').format(picked);
|
||||
_dateNaissanceController.text = DateFormat('dd/MM/yyyy').format(picked);
|
||||
} else {
|
||||
_dateEmbauche = picked;
|
||||
_dateEmbaucheController.text =
|
||||
DateFormat('dd/MM/yyyy').format(picked);
|
||||
_dateEmbaucheController.text = DateFormat('dd/MM/yyyy').format(picked);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -111,7 +123,7 @@ class _UserFormState extends State<UserForm> {
|
||||
// Gérer les erreurs spécifiques au sélecteur de date
|
||||
debugPrint('Erreur lors de la sélection de la date: $error');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Erreur lors de la sélection de la date'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
@@ -121,7 +133,7 @@ class _UserFormState extends State<UserForm> {
|
||||
// Gérer toutes les autres erreurs
|
||||
debugPrint('Exception lors de l\'affichage du sélecteur de date: $e');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Impossible d\'afficher le sélecteur de date'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
@@ -129,11 +141,14 @@ class _UserFormState extends State<UserForm> {
|
||||
}
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
// Méthode publique pour valider et récupérer l'utilisateur
|
||||
UserModel? validateAndGetUser() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final user = widget.user?.copyWith(
|
||||
return widget.user?.copyWith(
|
||||
username: _usernameController.text,
|
||||
firstName: _firstNameController.text,
|
||||
name: _nameController.text,
|
||||
sectName: _sectNameController.text,
|
||||
phone: _phoneController.text,
|
||||
mobile: _mobileController.text,
|
||||
email: _emailController.text,
|
||||
@@ -142,42 +157,113 @@ class _UserFormState extends State<UserForm> {
|
||||
dateEmbauche: _dateEmbauche,
|
||||
) ??
|
||||
UserModel(
|
||||
id: 0, // Sera remplacé par l'API
|
||||
id: 0,
|
||||
username: _usernameController.text,
|
||||
firstName: _firstNameController.text,
|
||||
name: _nameController.text,
|
||||
sectName: _sectNameController.text,
|
||||
phone: _phoneController.text,
|
||||
mobile: _mobileController.text,
|
||||
email: _emailController.text,
|
||||
fkTitre: _fkTitre,
|
||||
dateNaissance: _dateNaissance,
|
||||
dateEmbauche: _dateEmbauche,
|
||||
role: 1, // Valeur par défaut
|
||||
role: 1,
|
||||
createdAt: DateTime.now(),
|
||||
lastSyncedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
if (widget.onSubmit != null) {
|
||||
widget.onSubmit!(user);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isWideScreen = MediaQuery.of(context).size.width > 900;
|
||||
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Nom d'utilisateur (en lecture seule)
|
||||
CustomTextField(
|
||||
controller: _usernameController,
|
||||
label: "Nom d'utilisateur",
|
||||
readOnly: true, // Toujours en lecture seule
|
||||
prefixIcon: Icons.account_circle,
|
||||
),
|
||||
// Ligne 1: Username et Email (si écran large)
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _usernameController,
|
||||
label: "Nom d'utilisateur",
|
||||
readOnly: !widget.allowUsernameEdit, // Utiliser le paramètre
|
||||
prefixIcon: Icons.account_circle,
|
||||
isRequired: widget.allowUsernameEdit,
|
||||
validator: widget.allowUsernameEdit
|
||||
? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le nom d'utilisateur";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _emailController,
|
||||
label: "Email",
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
readOnly: widget.readOnly,
|
||||
isRequired: true, // Email toujours obligatoire
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer l'adresse email";
|
||||
}
|
||||
if (!value.contains('@') || !value.contains('.')) {
|
||||
return "Veuillez entrer une adresse email valide";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
// Version mobile: Username seul
|
||||
CustomTextField(
|
||||
controller: _usernameController,
|
||||
label: "Nom d'utilisateur",
|
||||
readOnly: !widget.allowUsernameEdit, // Utiliser le paramètre
|
||||
prefixIcon: Icons.account_circle,
|
||||
isRequired: widget.allowUsernameEdit, // Obligatoire si éditable
|
||||
validator: widget.allowUsernameEdit
|
||||
? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le nom d'utilisateur";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Email seul en mobile
|
||||
CustomTextField(
|
||||
controller: _emailController,
|
||||
label: "Email",
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
readOnly: widget.readOnly,
|
||||
isRequired: true, // Email toujours obligatoire
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer l'adresse email";
|
||||
}
|
||||
if (!value.contains('@') || !value.contains('.')) {
|
||||
return "Veuillez entrer une adresse email valide";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Titre (M. ou Mme)
|
||||
@@ -188,7 +274,7 @@ class _UserFormState extends State<UserForm> {
|
||||
"Titre",
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -225,115 +311,210 @@ class _UserFormState extends State<UserForm> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Prénom
|
||||
CustomTextField(
|
||||
controller: _firstNameController,
|
||||
label: "Prénom",
|
||||
readOnly: widget.readOnly,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le prénom";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Nom
|
||||
CustomTextField(
|
||||
controller: _nameController,
|
||||
label: "Nom",
|
||||
readOnly: widget.readOnly,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer le nom";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Téléphone fixe
|
||||
CustomTextField(
|
||||
controller: _phoneController,
|
||||
label: "Téléphone fixe",
|
||||
keyboardType: TextInputType.phone,
|
||||
readOnly: widget.readOnly,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(10),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 10) {
|
||||
return "Le numéro de téléphone doit contenir 10 chiffres";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Téléphone mobile
|
||||
CustomTextField(
|
||||
controller: _mobileController,
|
||||
label: "Téléphone mobile",
|
||||
keyboardType: TextInputType.phone,
|
||||
readOnly: widget.readOnly,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(10),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 10) {
|
||||
return "Le numéro de mobile doit contenir 10 chiffres";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Email
|
||||
CustomTextField(
|
||||
controller: _emailController,
|
||||
label: "Email",
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
readOnly: widget.readOnly,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Veuillez entrer l'adresse email";
|
||||
}
|
||||
if (!value.contains('@') || !value.contains('.')) {
|
||||
return "Veuillez entrer une adresse email valide";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Date de naissance
|
||||
CustomTextField(
|
||||
controller: _dateNaissanceController,
|
||||
label: "Date de naissance",
|
||||
readOnly: true,
|
||||
onTap: widget.readOnly ? null : () => _selectDate(context, true),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
color: theme.colorScheme.primary,
|
||||
// Ligne 2: Prénom et Nom
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _firstNameController,
|
||||
label: "Prénom",
|
||||
readOnly: widget.readOnly,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _nameController,
|
||||
label: "Nom",
|
||||
readOnly: widget.readOnly,
|
||||
validator: (value) => _validateNameOrSectName(value, true),
|
||||
onChanged: (value) {
|
||||
// Revalider sectName quand name change
|
||||
if (widget.allowSectNameEdit) {
|
||||
_formKey.currentState?.validate();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
// Version mobile: Prénom et nom séparés
|
||||
CustomTextField(
|
||||
controller: _firstNameController,
|
||||
label: "Prénom",
|
||||
readOnly: widget.readOnly,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
CustomTextField(
|
||||
controller: _nameController,
|
||||
label: "Nom",
|
||||
readOnly: widget.readOnly,
|
||||
validator: (value) => _validateNameOrSectName(value, true),
|
||||
onChanged: (value) {
|
||||
// Revalider sectName quand name change
|
||||
if (widget.allowSectNameEdit) {
|
||||
_formKey.currentState?.validate();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Date d'embauche
|
||||
CustomTextField(
|
||||
controller: _dateEmbaucheController,
|
||||
label: "Date d'embauche",
|
||||
readOnly: true,
|
||||
onTap: widget.readOnly ? null : () => _selectDate(context, false),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
color: theme.colorScheme.primary,
|
||||
// Ligne 2.5: Nom de tournée (sectName) - uniquement si éditable
|
||||
if (widget.allowSectNameEdit) ...[
|
||||
CustomTextField(
|
||||
controller: _sectNameController,
|
||||
label: "Nom de tournée",
|
||||
readOnly: widget.readOnly,
|
||||
validator: (value) => _validateNameOrSectName(value, false),
|
||||
onChanged: (value) {
|
||||
// Revalider name quand sectName change
|
||||
_formKey.currentState?.validate();
|
||||
},
|
||||
hintText: "Nom utilisé pour identifier la tournée",
|
||||
),
|
||||
),
|
||||
// Espace en bas du formulaire
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Ligne 3: Téléphones (fixe et mobile)
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _phoneController,
|
||||
label: "Téléphone fixe",
|
||||
keyboardType: TextInputType.phone,
|
||||
readOnly: widget.readOnly,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(10),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 10) {
|
||||
return "Le numéro doit contenir 10 chiffres";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _mobileController,
|
||||
label: "Téléphone mobile",
|
||||
keyboardType: TextInputType.phone,
|
||||
readOnly: widget.readOnly,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(10),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 10) {
|
||||
return "Le numéro doit contenir 10 chiffres";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
// Version mobile: Téléphones séparés
|
||||
CustomTextField(
|
||||
controller: _phoneController,
|
||||
label: "Téléphone fixe",
|
||||
keyboardType: TextInputType.phone,
|
||||
readOnly: widget.readOnly,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(10),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 10) {
|
||||
return "Le numéro doit contenir 10 chiffres";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
CustomTextField(
|
||||
controller: _mobileController,
|
||||
label: "Téléphone mobile",
|
||||
keyboardType: TextInputType.phone,
|
||||
readOnly: widget.readOnly,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(10),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 10) {
|
||||
return "Le numéro doit contenir 10 chiffres";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Ligne 4: Dates (naissance et embauche)
|
||||
if (isWideScreen)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _dateNaissanceController,
|
||||
label: "Date de naissance",
|
||||
readOnly: true,
|
||||
onTap: widget.readOnly ? null : () => _selectDate(context, true),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: CustomTextField(
|
||||
controller: _dateEmbaucheController,
|
||||
label: "Date d'embauche",
|
||||
readOnly: true,
|
||||
onTap: widget.readOnly ? null : () => _selectDate(context, false),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else ...[
|
||||
// Version mobile: Dates séparées
|
||||
CustomTextField(
|
||||
controller: _dateNaissanceController,
|
||||
label: "Date de naissance",
|
||||
readOnly: true,
|
||||
onTap: widget.readOnly ? null : () => _selectDate(context, true),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
CustomTextField(
|
||||
controller: _dateEmbaucheController,
|
||||
label: "Date d'embauche",
|
||||
readOnly: true,
|
||||
onTap: widget.readOnly ? null : () => _selectDate(context, false),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
@@ -360,7 +541,7 @@ class _UserFormState extends State<UserForm> {
|
||||
Text(
|
||||
label,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onBackground,
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@@ -368,3 +549,6 @@ class _UserFormState extends State<UserForm> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Exporter la classe State pour pouvoir l'utiliser avec GlobalKey
|
||||
typedef UserFormState = _UserFormState;
|
||||
|
||||
Reference in New Issue
Block a user