fix(#129): peaufiner le wizard famille (co-parent, hauteurs, à naître).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,6 +30,7 @@ class _CreateChild {
|
||||
final TextEditingController nomCtrl = TextEditingController();
|
||||
final TextEditingController dateCtrl = TextEditingController();
|
||||
String? genre;
|
||||
bool isUnborn = false;
|
||||
Uint8List? photoBytes;
|
||||
String? photoFilename;
|
||||
|
||||
@@ -92,9 +93,9 @@ class ParentDossierWizard extends StatefulWidget {
|
||||
|
||||
bool get isCreate => mode == ParentDossierWizardMode.create;
|
||||
|
||||
/// Hauteur corps modale famille — dérivée de [ValidationFormMetrics] (4 lignes).
|
||||
/// Hauteur corps modale famille — 4 lignes TF + marge pour le bandeau switch.
|
||||
static double get shellBodyHeight =>
|
||||
ValidationFormMetrics.shellBodyHeightForRows(4);
|
||||
ValidationFormMetrics.shellBodyHeightForRows(4) + 20;
|
||||
|
||||
@override
|
||||
State<ParentDossierWizard> createState() => _ParentDossierWizardState();
|
||||
@@ -319,60 +320,122 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('Ajouter un co-parent'),
|
||||
value: _hasCoParent,
|
||||
onChanged: (v) => setState(() => _hasCoParent = v),
|
||||
),
|
||||
if (!_hasCoParent)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text(
|
||||
'Un seul parent pour ce dossier.',
|
||||
style: TextStyle(color: Colors.black87),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
IdentityBlock.editable(
|
||||
title: 'Deuxième parent',
|
||||
nomController: _p2NomCtrl,
|
||||
prenomController: _p2PrenomCtrl,
|
||||
telephoneController: _p2TelCtrl,
|
||||
emailController: _p2EmailCtrl,
|
||||
adresseController: _p2AdresseCtrl,
|
||||
codePostalController: _p2CpCtrl,
|
||||
villeController: _p2VilleCtrl,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('Même adresse que le parent principal'),
|
||||
value: _sameAddress,
|
||||
onChanged: (v) => setState(() {
|
||||
_sameAddress = v;
|
||||
if (v) {
|
||||
_p2AdresseCtrl.text = _p1AdresseCtrl.text;
|
||||
_p2CpCtrl.text = _p1CpCtrl.text;
|
||||
_p2VilleCtrl.text = _p1VilleCtrl.text;
|
||||
}
|
||||
}),
|
||||
),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
'Deuxième parent',
|
||||
style: TextStyle(
|
||||
fontSize: ValidationFormMetrics.sectionTitleFontSize,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const Text(
|
||||
'Ajouter un co-parent',
|
||||
style: TextStyle(fontSize: 14, color: Colors.black87),
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Switch(
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
value: _hasCoParent,
|
||||
onChanged: (v) => setState(() => _hasCoParent = v),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (!_hasCoParent)
|
||||
const Text(
|
||||
'Un seul parent pour ce dossier.',
|
||||
style: TextStyle(color: Colors.black87),
|
||||
)
|
||||
else
|
||||
ValidationFormGrid(
|
||||
compact: true,
|
||||
rowLayout: IdentityBlock.rowLayout,
|
||||
rowFlex: IdentityBlock.rowFlex,
|
||||
fields: [
|
||||
ValidationLabeledField(
|
||||
label: 'Nom',
|
||||
field: ValidationEditableField(
|
||||
controller: _p2NomCtrl,
|
||||
inputFormatters: const [PersonNameInputFormatter()],
|
||||
),
|
||||
),
|
||||
ValidationLabeledField(
|
||||
label: 'Prénom',
|
||||
field: ValidationEditableField(
|
||||
controller: _p2PrenomCtrl,
|
||||
inputFormatters: const [PersonNameInputFormatter()],
|
||||
),
|
||||
),
|
||||
ValidationLabeledField(
|
||||
label: 'Téléphone',
|
||||
field: ValidationPhoneField(controller: _p2TelCtrl),
|
||||
),
|
||||
ValidationLabeledField(
|
||||
label: 'Email',
|
||||
field: ValidationEmailField(controller: _p2EmailCtrl),
|
||||
),
|
||||
ValidationLabeledField(
|
||||
label: 'Adresse (N° et Rue)',
|
||||
labelTrailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Même adresse',
|
||||
style: TextStyle(
|
||||
fontSize: ValidationFormMetrics.fieldLabelFontSize,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Switch(
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
value: _sameAddress,
|
||||
onChanged: (v) => setState(() {
|
||||
_sameAddress = v;
|
||||
if (v) _copyP1AddressToP2();
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
field: ValidationEditableField(
|
||||
controller: _p2AdresseCtrl,
|
||||
enabled: !_sameAddress,
|
||||
),
|
||||
),
|
||||
ValidationLabeledField(
|
||||
label: 'Code postal',
|
||||
field: ValidationPostalCodeField(
|
||||
controller: _p2CpCtrl,
|
||||
enabled: !_sameAddress,
|
||||
),
|
||||
),
|
||||
ValidationLabeledField(
|
||||
label: 'Ville',
|
||||
field: ValidationEditableField(
|
||||
controller: _p2VilleCtrl,
|
||||
enabled: !_sameAddress,
|
||||
inputFormatters: const [PersonNameInputFormatter()],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _copyP1AddressToP2() {
|
||||
_p2AdresseCtrl.text = _p1AdresseCtrl.text;
|
||||
_p2CpCtrl.text = _p1CpCtrl.text;
|
||||
_p2VilleCtrl.text = _p1VilleCtrl.text;
|
||||
}
|
||||
|
||||
Widget _buildParent2Step() {
|
||||
if (_dossier.parents.length < 2) {
|
||||
return const Padding(
|
||||
@@ -643,11 +706,12 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 40, 8),
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 14, 8),
|
||||
child: ValidationLabeledField(
|
||||
label: 'Prénom',
|
||||
field: ValidationEditableField(
|
||||
controller: child.prenomCtrl,
|
||||
hintText: child.isUnborn ? 'Facultatif' : null,
|
||||
inputFormatters: const [PersonNameInputFormatter()],
|
||||
),
|
||||
),
|
||||
@@ -715,7 +779,36 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
ValidationLabeledField(
|
||||
label: 'Date de naissance',
|
||||
label: child.isUnborn
|
||||
? 'Date prévisionnelle'
|
||||
: 'Date de naissance',
|
||||
labelTrailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'À naître',
|
||||
style: TextStyle(
|
||||
fontSize: ValidationFormMetrics
|
||||
.fieldLabelFontSize,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Switch(
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
value: child.isUnborn,
|
||||
onChanged: (v) => setState(() {
|
||||
child.isUnborn = v;
|
||||
if (!v && child.genre == 'Autre') {
|
||||
child.genre = null;
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
field: ValidationEditableField(
|
||||
controller: child.dateCtrl,
|
||||
hintText: 'jj / mm / aaaa',
|
||||
@@ -766,20 +859,28 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
|
||||
Widget _genreDropdown(int index) {
|
||||
final child = _children[index];
|
||||
final items = <DropdownMenuItem<String>>[
|
||||
const DropdownMenuItem(value: 'H', child: Text('Garçon')),
|
||||
const DropdownMenuItem(value: 'F', child: Text('Fille')),
|
||||
if (child.isUnborn)
|
||||
const DropdownMenuItem(value: 'Autre', child: Text('Inconnu')),
|
||||
];
|
||||
final value = child.genre != null &&
|
||||
items.any((e) => e.value == child.genre)
|
||||
? child.genre
|
||||
: null;
|
||||
return SizedBox(
|
||||
height: ValidationFormMetrics.fieldHeight,
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: child.genre,
|
||||
key: ValueKey('genre-$index-${child.isUnborn}'),
|
||||
value: value,
|
||||
isExpanded: true,
|
||||
hint: Text(
|
||||
'Sélectionner',
|
||||
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
|
||||
),
|
||||
decoration: ValidationFieldDecoration.input(),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'H', child: Text('Garçon')),
|
||||
DropdownMenuItem(value: 'F', child: Text('Fille')),
|
||||
],
|
||||
items: items,
|
||||
onChanged: (v) => setState(() => child.genre = v),
|
||||
),
|
||||
);
|
||||
@@ -1118,6 +1219,10 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
normalizeEmailText(_p1EmailCtrl.text)) {
|
||||
return 'L’email du co-parent doit être différent de celui du parent principal.';
|
||||
}
|
||||
if (_sameAddress) {
|
||||
_copyP1AddressToP2();
|
||||
return null;
|
||||
}
|
||||
if (_p2AdresseCtrl.text.trim().isEmpty) {
|
||||
return 'L’adresse du co-parent est requise.';
|
||||
}
|
||||
@@ -1134,14 +1239,27 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
for (var i = 0; i < _children.length; i++) {
|
||||
final c = _children[i];
|
||||
final label = 'Enfant ${i + 1}';
|
||||
if (c.prenomCtrl.text.trim().length < 2) {
|
||||
return '$label : le prénom doit contenir au moins 2 caractères.';
|
||||
}
|
||||
if (parseFrDateToIso(c.dateCtrl.text) == null) {
|
||||
return '$label : indiquez une date de naissance valide (jj/mm/aaaa).';
|
||||
}
|
||||
if (c.genre != 'H' && c.genre != 'F') {
|
||||
return '$label : indiquez le genre (Garçon ou Fille).';
|
||||
final prenom = c.prenomCtrl.text.trim();
|
||||
if (c.isUnborn) {
|
||||
if (prenom.isNotEmpty && prenom.length < 2) {
|
||||
return '$label : le prénom doit contenir au moins 2 caractères.';
|
||||
}
|
||||
if (parseFrDateToIso(c.dateCtrl.text) == null) {
|
||||
return '$label : indiquez une date prévisionnelle valide (jj/mm/aaaa).';
|
||||
}
|
||||
if (c.genre != 'H' && c.genre != 'F' && c.genre != 'Autre') {
|
||||
return '$label : indiquez le genre (Garçon, Fille ou Inconnu).';
|
||||
}
|
||||
} else {
|
||||
if (prenom.length < 2) {
|
||||
return '$label : le prénom doit contenir au moins 2 caractères.';
|
||||
}
|
||||
if (parseFrDateToIso(c.dateCtrl.text) == null) {
|
||||
return '$label : indiquez une date de naissance valide (jj/mm/aaaa).';
|
||||
}
|
||||
if (c.genre != 'H' && c.genre != 'F') {
|
||||
return '$label : indiquez le genre (Garçon ou Fille).';
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -1207,17 +1325,29 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
final nom = nomRaw.isNotEmpty
|
||||
? formatPersonNameCase(nomRaw)
|
||||
: formatPersonNameCase(_p1NomCtrl.text);
|
||||
final birthIso = parseFrDateToIso(c.dateCtrl.text);
|
||||
final dateIso = parseFrDateToIso(c.dateCtrl.text);
|
||||
|
||||
final map = <String, dynamic>{
|
||||
'prenom': prenom,
|
||||
'nom': nom,
|
||||
'date_naissance': birthIso,
|
||||
'genre': c.genre,
|
||||
'consent_photo': true,
|
||||
'grossesse_multiple': false,
|
||||
};
|
||||
|
||||
if (prenom.length >= 2) {
|
||||
map['prenom'] = prenom;
|
||||
}
|
||||
if (nom.length >= 2) {
|
||||
map['nom'] = nom;
|
||||
}
|
||||
|
||||
if (c.isUnborn) {
|
||||
if (dateIso != null) {
|
||||
map['date_previsionnelle_naissance'] = dateIso;
|
||||
}
|
||||
} else if (dateIso != null) {
|
||||
map['date_naissance'] = dateIso;
|
||||
}
|
||||
|
||||
final bytes = c.photoBytes;
|
||||
if (bytes != null && bytes.isNotEmpty) {
|
||||
final mime = _imageMimeForBytes(bytes);
|
||||
@@ -1243,16 +1373,13 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
|
||||
};
|
||||
|
||||
if (_hasCoParent) {
|
||||
final sameAddr =
|
||||
_p2AdresseCtrl.text.trim() == _p1AdresseCtrl.text.trim() &&
|
||||
_p2CpCtrl.text.trim() == _p1CpCtrl.text.trim() &&
|
||||
_p2VilleCtrl.text.trim() == _p1VilleCtrl.text.trim();
|
||||
if (_sameAddress) _copyP1AddressToP2();
|
||||
body['co_parent_email'] = normalizeEmailText(_p2EmailCtrl.text);
|
||||
body['co_parent_prenom'] = formatPersonNameCase(_p2PrenomCtrl.text);
|
||||
body['co_parent_nom'] = formatPersonNameCase(_p2NomCtrl.text);
|
||||
body['co_parent_telephone'] = normalizePhone(_p2TelCtrl.text);
|
||||
body['co_parent_meme_adresse'] = sameAddr;
|
||||
if (!sameAddr) {
|
||||
body['co_parent_meme_adresse'] = _sameAddress;
|
||||
if (!_sameAddress) {
|
||||
body['co_parent_adresse'] = _p2AdresseCtrl.text.trim();
|
||||
body['co_parent_code_postal'] = _p2CpCtrl.text.trim();
|
||||
body['co_parent_ville'] = formatPersonNameCase(_p2VilleCtrl.text);
|
||||
|
||||
Reference in New Issue
Block a user