petitspas/frontend/lib/widgets/common/identity_block.dart
Julien Martin 003fe6b762 feat(#131): fiches parent/AM éditable, placement AM↔enfant, statuts garde/sans_garde
Squash merge develop → master.

- Fiche parent éditable (co-parent, PATCH fiche, GET /parents)
- Fiche AM 3 onglets (PATCH fiche, rattacher/détacher enfants)
- Table enfants_assistantes_maternelles + enum garde/sans_garde
- Migration SQL + BDD.sql canonique
- Correctifs recette : @Get() parents, DTO fiche AM, fix NIR

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 22:49:57 +02:00

279 lines
8.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/dossier_unifie.dart';
import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/utils/phone_utils.dart';
import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart';
/// Valeurs affichées dans un [IdentityBlock] en lecture seule.
class IdentityValues {
final String nom;
final String prenom;
final String telephone;
final String email;
final String adresse;
final String codePostal;
final String ville;
const IdentityValues({
required this.nom,
required this.prenom,
required this.telephone,
required this.email,
required this.adresse,
required this.codePostal,
required this.ville,
});
static String _fmt(String? s, String empty) {
final t = (s ?? '').trim();
return t.isEmpty ? empty : t;
}
static String _fmtPhone(String? s, String empty) {
final tel = _fmt(s, empty);
return tel != empty ? formatPhoneForDisplay(tel) : tel;
}
factory IdentityValues.fromUser(
AppUser user, {
String emptyLabel = 'Non défini',
}) {
return IdentityValues(
nom: _fmt(user.nom, emptyLabel),
prenom: _fmt(user.prenom, emptyLabel),
telephone: _fmtPhone(user.telephone, emptyLabel),
email: _fmt(user.email, emptyLabel),
adresse: _fmt(user.adresse, emptyLabel),
codePostal: _fmt(user.codePostal, emptyLabel),
ville: _fmt(user.ville, emptyLabel),
);
}
factory IdentityValues.fromParentDossier(
ParentDossier parent, {
String emptyLabel = 'Non défini',
}) {
return IdentityValues(
nom: _fmt(parent.nom, emptyLabel),
prenom: _fmt(parent.prenom, emptyLabel),
telephone: _fmtPhone(parent.telephone, emptyLabel),
email: _fmt(parent.email, emptyLabel),
adresse: _fmt(parent.adresse, emptyLabel),
codePostal: _fmt(parent.codePostal, emptyLabel),
ville: _fmt(parent.ville, emptyLabel),
);
}
}
/// Bloc identité : Nom/Prénom, Tél/Email, Adresse, CP/Ville.
/// Grille partagée (création de compte, validation AM/famille, fiches admin, etc.).
class IdentityBlock extends StatelessWidget { final String? title;
final String? _nom;
final String? _prenom;
final String? _telephone;
final String? _email;
final String? _adresse;
final String? _codePostal;
final String? _ville;
final TextEditingController? _nomCtrl;
final TextEditingController? _prenomCtrl;
final TextEditingController? _telCtrl;
final TextEditingController? _emailCtrl;
final TextEditingController? _adresseCtrl;
final TextEditingController? _cpCtrl;
final TextEditingController? _villeCtrl;
static const List<int> rowLayout = [2, 2, 1, 2];
static const Map<int, List<int>> rowFlex = {3: [2, 5]};
const IdentityBlock.readOnly({
super.key,
this.title,
required String nom,
required String prenom,
required String telephone,
required String email,
required String adresse,
required String codePostal,
required String ville,
}) : _nom = nom,
_prenom = prenom,
_telephone = telephone,
_email = email,
_adresse = adresse,
_codePostal = codePostal,
_ville = ville,
_nomCtrl = null,
_prenomCtrl = null,
_telCtrl = null,
_emailCtrl = null,
_adresseCtrl = null,
_cpCtrl = null,
_villeCtrl = null;
const IdentityBlock.editable({
super.key,
this.title,
required TextEditingController nomController,
required TextEditingController prenomController,
required TextEditingController telephoneController,
required TextEditingController emailController,
required TextEditingController adresseController,
required TextEditingController codePostalController,
required TextEditingController villeController,
}) : _nom = null,
_prenom = null,
_telephone = null,
_email = null,
_adresse = null,
_codePostal = null,
_ville = null,
_nomCtrl = nomController,
_prenomCtrl = prenomController,
_telCtrl = telephoneController,
_emailCtrl = emailController,
_adresseCtrl = adresseController,
_cpCtrl = codePostalController,
_villeCtrl = villeController;
/// Lecture seule à partir de [IdentityValues].
factory IdentityBlock.readOnlyValues({
Key? key,
String? title,
required IdentityValues values,
}) {
return IdentityBlock.readOnly(
key: key,
title: title,
nom: values.nom,
prenom: values.prenom,
telephone: values.telephone,
email: values.email,
adresse: values.adresse,
codePostal: values.codePostal,
ville: values.ville,
);
}
/// Lecture seule depuis un [AppUser] (validation AM, fiche AM, etc.).
factory IdentityBlock.readOnlyFromUser(
AppUser user, {
Key? key,
String? title,
String emptyLabel = 'Non défini',
}) {
return IdentityBlock.readOnlyValues(
key: key,
title: title,
values: IdentityValues.fromUser(user, emptyLabel: emptyLabel),
);
}
/// Lecture seule depuis un [ParentDossier] (validation famille).
factory IdentityBlock.readOnlyFromParentDossier(
ParentDossier parent, {
Key? key,
String? title,
String emptyLabel = 'Non défini',
}) {
return IdentityBlock.readOnlyValues(
key: key,
title: title,
values: IdentityValues.fromParentDossier(
parent,
emptyLabel: emptyLabel,
),
);
}
bool get _isEditable => _nomCtrl != null;
@override
Widget build(BuildContext context) {
if (_isEditable) {
return ValidationFormGrid(
title: title,
rowLayout: rowLayout,
rowFlex: rowFlex,
fields: [
ValidationLabeledField(
label: 'Nom',
field: ValidationEditableField(controller: _nomCtrl!),
),
ValidationLabeledField(
label: 'Prénom',
field: ValidationEditableField(controller: _prenomCtrl!),
),
ValidationLabeledField(
label: 'Téléphone',
field: ValidationEditableField(
controller: _telCtrl!,
keyboardType: TextInputType.phone,
inputFormatters: frenchPhoneInputFormatters,
),
),
ValidationLabeledField(
label: 'Email',
field: ValidationEditableField(
controller: _emailCtrl!,
keyboardType: TextInputType.emailAddress,
),
),
ValidationLabeledField(
label: 'Adresse (N° et Rue)',
field: ValidationEditableField(controller: _adresseCtrl!),
),
ValidationLabeledField(
label: 'Code postal',
field: ValidationEditableField(
controller: _cpCtrl!,
keyboardType: TextInputType.number,
),
),
ValidationLabeledField(
label: 'Ville',
field: ValidationEditableField(controller: _villeCtrl!),
),
],
);
}
return ValidationFormGrid(
title: title,
rowLayout: rowLayout,
rowFlex: rowFlex,
fields: [
ValidationLabeledField(
label: 'Nom',
field: ValidationReadOnlyField(value: _nom!),
),
ValidationLabeledField(
label: 'Prénom',
field: ValidationReadOnlyField(value: _prenom!),
),
ValidationLabeledField(
label: 'Téléphone',
field: ValidationReadOnlyField(value: _telephone!),
),
ValidationLabeledField(
label: 'Email',
field: ValidationReadOnlyField(value: _email!),
),
ValidationLabeledField(
label: 'Adresse (N° et Rue)',
field: ValidationReadOnlyField(value: _adresse!),
),
ValidationLabeledField(
label: 'Code postal',
field: ValidationReadOnlyField(value: _codePostal!),
),
ValidationLabeledField(
label: 'Ville',
field: ValidationReadOnlyField(value: _ville!),
),
],
);
}
}