feat(#96): différencier la consultation admin et le mode édition

Affiche une identité visuelle dédiée pour les super admins et adapte l’action par ligne (oeil en lecture seule, crayon en édition) avec modale strictement read-only quand l’utilisateur n’a pas les droits.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-24 21:48:11 +01:00
co-authored by Cursor
parent d8572e7fd6
commit e2ebc6a0a1
3 changed files with 108 additions and 35 deletions
@@ -9,12 +9,14 @@ class AdminUserFormDialog extends StatefulWidget {
final AppUser? initialUser;
final bool withRelais;
final bool adminMode;
final bool readOnly;
const AdminUserFormDialog({
super.key,
this.initialUser,
this.withRelais = true,
this.adminMode = false,
this.readOnly = false,
});
@override
@@ -194,6 +196,7 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
}
Future<void> _submit() async {
if (widget.readOnly) return;
if (_isSubmitting) return;
if (!_formKey.currentState!.validate()) return;
@@ -304,6 +307,7 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
}
Future<void> _delete() async {
if (widget.readOnly) return;
if (!_isEditMode || _isSubmitting) return;
final confirmed = await showDialog<bool>(
@@ -363,15 +367,19 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
Expanded(
child: Text(
_isEditMode
? (widget.adminMode
? 'Modifier un administrateur'
: 'Modifier un gestionnaire')
? (widget.readOnly
? (widget.adminMode
? 'Consulter un administrateur'
: 'Consulter un gestionnaire')
: (widget.adminMode
? 'Modifier un administrateur'
: 'Modifier un gestionnaire'))
: (widget.adminMode
? 'Créer un administrateur'
: 'Créer un gestionnaire'),
),
),
if (_isEditMode)
if (_isEditMode && !widget.readOnly)
IconButton(
icon: const Icon(Icons.close),
tooltip: 'Fermer',
@@ -416,7 +424,12 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
),
),
actions: [
if (_isEditMode) ...[
if (widget.readOnly) ...[
FilledButton(
onPressed: _isSubmitting ? null : () => Navigator.of(context).pop(false),
child: const Text('Fermer'),
),
] else if (_isEditMode) ...[
OutlinedButton(
onPressed: _isSubmitting ? null : _delete,
style: OutlinedButton.styleFrom(foregroundColor: Colors.red.shade700),
@@ -458,42 +471,46 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
Widget _buildNomField() {
return TextFormField(
controller: _nomController,
readOnly: widget.readOnly,
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Nom',
border: OutlineInputBorder(),
),
validator: (v) => _required(v, 'Nom'),
validator: widget.readOnly ? null : (v) => _required(v, 'Nom'),
);
}
Widget _buildPrenomField() {
return TextFormField(
controller: _prenomController,
readOnly: widget.readOnly,
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Prénom',
border: OutlineInputBorder(),
),
validator: (v) => _required(v, 'Prénom'),
validator: widget.readOnly ? null : (v) => _required(v, 'Prénom'),
);
}
Widget _buildEmailField() {
return TextFormField(
controller: _emailController,
readOnly: widget.readOnly,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
validator: _validateEmail,
validator: widget.readOnly ? null : _validateEmail,
);
}
Widget _buildPasswordField() {
return TextFormField(
controller: _passwordController,
readOnly: widget.readOnly,
obscureText: _obscurePassword,
enableSuggestions: false,
autocorrect: false,
@@ -505,38 +522,43 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
? 'Nouveau mot de passe'
: 'Mot de passe',
border: const OutlineInputBorder(),
suffixIcon: ExcludeFocus(
child: IconButton(
focusNode: _passwordToggleFocusNode,
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
),
),
),
suffixIcon: widget.readOnly
? null
: ExcludeFocus(
child: IconButton(
focusNode: _passwordToggleFocusNode,
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
),
),
),
),
validator: _validatePassword,
validator: widget.readOnly ? null : _validatePassword,
);
}
Widget _buildTelephoneField() {
return TextFormField(
controller: _telephoneController,
readOnly: widget.readOnly,
keyboardType: TextInputType.phone,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10),
_FrenchPhoneNumberFormatter(),
],
inputFormatters: widget.readOnly
? null
: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10),
_FrenchPhoneNumberFormatter(),
],
decoration: const InputDecoration(
labelText: 'Téléphone (ex: 06 12 34 56 78)',
border: OutlineInputBorder(),
),
validator: _validatePhone,
validator: widget.readOnly ? null : _validatePhone,
);
}
@@ -568,7 +590,7 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
),
),
],
onChanged: _isLoadingRelais
onChanged: (_isLoadingRelais || widget.readOnly)
? null
: (value) {
setState(() {