fix(#156): caler la modale AM sur les TF et unifier create/review.

ValidationFormMetrics (titres, TF, écarts) pilote la hauteur ; photo
étirée sur le corps ; mêmes widgets create et validation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-23 16:49:59 +02:00
co-authored by Cursor
parent e3552667bc
commit 8ee2ca8ea6
5 changed files with 272 additions and 162 deletions
@@ -33,7 +33,8 @@ class _AmDossierCreateModalState extends State<AmDossierCreateModal> {
} }
static const double _modalWidth = 930; static const double _modalWidth = 930;
static const double _bodyHeight = 435; /// Hauteur calculée depuis 4 lignes de TF (voir [AmDossierWizard.shellBodyHeight]).
static double get _bodyHeight => AmDossierWizard.shellBodyHeight;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -76,6 +76,10 @@ class AmDossierWizard extends StatefulWidget {
bool get isCreate => mode == AmDossierWizardMode.create; bool get isCreate => mode == AmDossierWizardMode.create;
/// Hauteur corps modale AM — dérivée de [ValidationFormMetrics] (4 lignes).
static double get shellBodyHeight =>
ValidationFormMetrics.shellBodyHeightForRows(4);
@override @override
State<AmDossierWizard> createState() => _AmDossierWizardState(); State<AmDossierWizard> createState() => _AmDossierWizardState();
} }
@@ -584,56 +588,45 @@ class _AmDossierWizardState extends State<AmDossierWizard> {
Widget _buildStep0() { Widget _buildStep0() {
if (_isCreate) { if (_isCreate) {
return LayoutBuilder( return IdentityBlock.editable(
builder: (context, constraints) { title: 'Identité et coordonnées',
return SingleChildScrollView( nomController: _nomCtrl,
child: ConstrainedBox( prenomController: _prenomCtrl,
constraints: BoxConstraints(minWidth: constraints.maxWidth), telephoneController: _telCtrl,
child: IdentityBlock.editable( emailController: _emailCtrl,
title: 'Identité et coordonnées', adresseController: _adresseCtrl,
nomController: _nomCtrl, codePostalController: _cpCtrl,
prenomController: _prenomCtrl, villeController: _villeCtrl,
telephoneController: _telCtrl,
emailController: _emailCtrl,
adresseController: _adresseCtrl,
codePostalController: _cpCtrl,
villeController: _villeCtrl,
),
),
);
},
); );
} }
final u = _dossier.user; final u = _dossier.user;
return LayoutBuilder( return IdentityBlock.readOnlyFromUser(
builder: (context, constraints) { u,
return SingleChildScrollView( title: 'Identité et coordonnées',
child: ConstrainedBox( emptyLabel: '',
constraints: BoxConstraints(minWidth: constraints.maxWidth),
child: IdentityBlock.readOnlyFromUser(
u,
title: 'Identité et coordonnées',
emptyLabel: '',
),
),
);
},
); );
} }
Widget _buildStep1() { Widget _buildStep1() {
// Modale calée sur les TF ; photo étirée sur toute la hauteur utile
// (largeur = [AdminAmPhotoFrame.columnWidthForHeight], cadre inclus).
return LayoutBuilder( return LayoutBuilder(
builder: (context, c) { builder: (context, c) {
final maxRowW = c.maxWidth; final maxRowW = c.maxWidth;
final maxRowH = c.maxHeight; final maxRowH = c.maxHeight.clamp(0.0, double.infinity);
const photoHeaderH = 0.0;
final bodyH = (maxRowH - photoHeaderH).clamp(0.0, double.infinity);
final idealPhotoW = bodyH * _idPhotoAspectRatio + 16;
final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth) final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth)
.clamp(0.0, double.infinity); .clamp(0.0, double.infinity);
var photoW = idealPhotoW.clamp(_photoColumnMinWidth, 360.0); var photoW = AdminAmPhotoFrame.columnWidthForHeight(maxRowH)
.clamp(_photoColumnMinWidth, 360.0);
if (photoW > maxPhotoW) photoW = maxPhotoW; if (photoW > maxPhotoW) photoW = maxPhotoW;
photoW = photoW.clamp(0.0, maxRowW - _photoProGap);
final form = _isCreate
? _buildCreateProFields()
: ValidationDetailSection(
title: 'Dossier professionnel',
fields: _photoProFields(_dossier),
rowLayout: _photoProRowLayout,
);
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -651,22 +644,9 @@ class _AmDossierWizardState extends State<AmDossierWizard> {
), ),
const SizedBox(width: _photoProGap), const SizedBox(width: _photoProGap),
Expanded( Expanded(
child: LayoutBuilder( child: Align(
builder: (context, constraints) { alignment: Alignment.topCenter,
return SingleChildScrollView( child: form,
child: ConstrainedBox(
constraints:
BoxConstraints(minWidth: constraints.maxWidth),
child: _isCreate
? _buildCreateProFields()
: ValidationDetailSection(
title: 'Dossier professionnel',
fields: _photoProFields(_dossier),
rowLayout: _photoProRowLayout,
),
),
);
},
), ),
), ),
], ],
@@ -6,6 +6,50 @@ import 'package:p_tits_pas/utils/phone_utils.dart';
import 'package:p_tits_pas/utils/postal_utils.dart'; import 'package:p_tits_pas/utils/postal_utils.dart';
import 'admin_detail_modal.dart'; import 'admin_detail_modal.dart';
/// Réglages des formulaires validation / wizard AM — **jouer sur ces 3 leviers**.
class ValidationFormMetrics {
ValidationFormMetrics._();
// --- 1. Titres de section ---
static const double sectionTitleFontSize = 16;
static const double sectionTitleGapBelow = 12;
// --- 2. TF : texte intérieur + padding vertical (= hauteur) ---
static const double fieldTextFontSize = 14;
static const double fieldContentPaddingV = 12;
static const double fieldContentPaddingH = 12;
/// Hauteur estimée du TF (texte + padding haut/bas + bordure).
static const double fieldHeight =
fieldTextFontSize + fieldContentPaddingV * 2 + 4;
// --- 3. Espace entre les lignes de TF ---
static const double rowGapBelow = 12;
// Libellé au-dessus du TF (titre du champ)
static const double fieldLabelFontSize = 13;
static const double fieldLabelGapBelow = 4;
static const TextStyle fieldTextStyle = TextStyle(
color: Colors.black87,
fontSize: fieldTextFontSize,
);
static double get sectionTitleBlockHeight =>
sectionTitleFontSize + sectionTitleGapBelow;
static double get labeledRowHeight =>
fieldLabelFontSize + fieldLabelGapBelow + fieldHeight + rowGapBelow;
/// Corps modale AM : padding wizard + titre + [rows] lignes + nav.
static double shellBodyHeightForRows(int rows) =>
20 * 2 + // padding wizard
4 + // espace haut
sectionTitleBlockHeight +
rows * labeledRowHeight +
24 + // avant nav
44; // boutons
}
/// Bloc type formulaire (titre de section + champs read-only) pour les modales de validation. /// Bloc type formulaire (titre de section + champs read-only) pour les modales de validation.
/// [rowLayout] : même disposition que la création de compte, ex. [2, 2, 1, 2] = ligne de 2, ligne de 2, plein largeur, ligne de 2. /// [rowLayout] : même disposition que la création de compte, ex. [2, 2, 1, 2] = ligne de 2, ligne de 2, plein largeur, ligne de 2.
/// [rowFlex] : flex par index de ligne (optionnel). Ex. {3: [2, 5]} = 4e ligne : code postal étroit (2), ville large (5). /// [rowFlex] : flex par index de ligne (optionnel). Ex. {3: [2, 5]} = 4e ligne : code postal étroit (2), ville large (5).
@@ -20,12 +64,16 @@ class ValidationDetailSection extends StatelessWidget {
/// Flex par ligne (index de ligne -> [flex1, flex2, ...]). Ex. {3: [2, 5]} pour Code postal | Ville. /// Flex par ligne (index de ligne -> [flex1, flex2, ...]). Ex. {3: [2, 5]} pour Code postal | Ville.
final Map<int, List<int>>? rowFlex; final Map<int, List<int>>? rowFlex;
/// Remplit la hauteur disponible (wizard AM étapes 12).
final bool expandVertically;
const ValidationDetailSection({ const ValidationDetailSection({
super.key, super.key,
this.title, this.title,
required this.fields, required this.fields,
this.rowLayout, this.rowLayout,
this.rowFlex, this.rowFlex,
this.expandVertically = false,
}); });
@override @override
@@ -34,6 +82,7 @@ class ValidationDetailSection extends StatelessWidget {
title: title, title: title,
rowLayout: rowLayout, rowLayout: rowLayout,
rowFlex: rowFlex, rowFlex: rowFlex,
expandVertically: expandVertically,
fields: fields fields: fields
.map( .map(
(f) => ValidationLabeledField( (f) => ValidationLabeledField(
@@ -53,6 +102,8 @@ class ValidationFormGrid extends StatelessWidget {
final List<int>? rowLayout; final List<int>? rowLayout;
final Map<int, List<int>>? rowFlex; final Map<int, List<int>>? rowFlex;
final bool compact; final bool compact;
/// Répartit la hauteur dispo entre les lignes (remplit le blanc sans scroll).
final bool expandVertically;
const ValidationFormGrid({ const ValidationFormGrid({
super.key, super.key,
@@ -61,6 +112,7 @@ class ValidationFormGrid extends StatelessWidget {
this.rowLayout, this.rowLayout,
this.rowFlex, this.rowFlex,
this.compact = false, this.compact = false,
this.expandVertically = false,
}); });
@override @override
@@ -68,7 +120,7 @@ class ValidationFormGrid extends StatelessWidget {
final layout = rowLayout ?? List.filled(fields.length, 1); final layout = rowLayout ?? List.filled(fields.length, 1);
int index = 0; int index = 0;
int rowIndex = 0; int rowIndex = 0;
final rows = <Widget>[]; final rowWidgets = <Widget>[];
for (final count in layout) { for (final count in layout) {
if (index >= fields.length) break; if (index >= fields.length) break;
final rowFields = fields.skip(index).take(count).toList(); final rowFields = fields.skip(index).take(count).toList();
@@ -76,48 +128,73 @@ class ValidationFormGrid extends StatelessWidget {
if (rowFields.isEmpty) continue; if (rowFields.isEmpty) continue;
final flexForRow = rowFlex?[rowIndex]; final flexForRow = rowFlex?[rowIndex];
rowIndex++; rowIndex++;
final labeled = rowFields
.map(
(f) => ValidationLabeledField(
label: f.label,
field: f.field,
expand: expandVertically,
),
)
.toList();
Widget row;
if (count == 1) { if (count == 1) {
rows.add(Padding( row = labeled.first;
padding: EdgeInsets.only(bottom: compact ? 8 : 12),
child: rowFields.first,
));
} else { } else {
rows.add(Padding( row = Row(
padding: EdgeInsets.only(bottom: compact ? 8 : 12), crossAxisAlignment: expandVertically
child: Row( ? CrossAxisAlignment.stretch
crossAxisAlignment: CrossAxisAlignment.start, : CrossAxisAlignment.start,
children: [ children: [
for (int i = 0; i < rowFields.length; i++) ...[ for (int i = 0; i < labeled.length; i++) ...[
if (i > 0) SizedBox(width: compact ? 12 : 16), if (i > 0) SizedBox(width: compact ? 12 : 16),
Expanded( Expanded(
flex: (flexForRow != null && i < flexForRow.length) flex: (flexForRow != null && i < flexForRow.length)
? flexForRow[i] ? flexForRow[i]
: 1, : 1,
child: rowFields[i], child: labeled[i],
), ),
],
], ],
],
);
}
if (expandVertically) {
rowWidgets.add(Expanded(child: row));
} else {
rowWidgets.add(Padding(
padding: EdgeInsets.only(
bottom: compact ? 8 : ValidationFormMetrics.rowGapBelow,
), ),
child: row,
)); ));
} }
} }
final showTitle = title != null && title!.trim().isNotEmpty; final showTitle = title != null && title!.trim().isNotEmpty;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min, mainAxisSize: expandVertically ? MainAxisSize.max : MainAxisSize.min,
children: [ children: [
if (showTitle) ...[ if (showTitle) ...[
Text( Text(
title!.trim(), title!.trim(),
style: TextStyle( style: TextStyle(
fontSize: compact ? 15 : 16, fontSize: compact
? ValidationFormMetrics.sectionTitleFontSize - 1
: ValidationFormMetrics.sectionTitleFontSize,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.black87, color: Colors.black87,
), ),
), ),
SizedBox(height: compact ? 8 : 12), SizedBox(
height: compact
? 8
: ValidationFormMetrics.sectionTitleGapBelow,
),
], ],
...rows, ...rowWidgets,
], ],
); );
} }
@@ -134,8 +211,8 @@ class ValidationFieldDecoration {
fillColor: Colors.grey.shade50, fillColor: Colors.grey.shade50,
hintText: hint, hintText: hint,
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
horizontal: compact ? 10 : 12, horizontal: compact ? 10 : ValidationFormMetrics.fieldContentPaddingH,
vertical: compact ? 7 : 10, vertical: compact ? 7 : ValidationFormMetrics.fieldContentPaddingV,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
@@ -184,29 +261,31 @@ class ValidationFieldDecoration {
class ValidationLabeledField extends StatelessWidget { class ValidationLabeledField extends StatelessWidget {
final String label; final String label;
final Widget field; final Widget field;
final bool expand;
const ValidationLabeledField({ const ValidationLabeledField({
super.key, super.key,
required this.label, required this.label,
required this.field, required this.field,
this.expand = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: expand ? MainAxisSize.max : MainAxisSize.min,
children: [ children: [
Text( Text(
label, label,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: ValidationFormMetrics.fieldLabelFontSize,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.grey.shade700, color: Colors.grey.shade700,
), ),
), ),
const SizedBox(height: 4), SizedBox(height: ValidationFormMetrics.fieldLabelGapBelow),
field, if (expand) Expanded(child: field) else field,
], ],
); );
} }
@@ -268,13 +347,16 @@ class ValidationEditableField extends StatelessWidget {
); );
} }
if (!compact) { if (!compact) {
return TextField( return _validationFieldFillHeight(
controller: controller, TextField(
keyboardType: keyboardType, controller: controller,
inputFormatters: inputFormatters, keyboardType: keyboardType,
maxLines: 1, inputFormatters: inputFormatters,
style: const TextStyle(color: Colors.black87, fontSize: 14), maxLines: 1,
decoration: ValidationFieldDecoration.input(hint: hintText), textAlignVertical: TextAlignVertical.center,
style: ValidationFormMetrics.fieldTextStyle,
decoration: ValidationFieldDecoration.input(hint: hintText),
),
); );
} }
return SizedBox( return SizedBox(
@@ -299,6 +381,20 @@ class ValidationEditableField extends StatelessWidget {
} }
} }
/// Étire le champ si le parent impose une hauteur (grille [expandVertically]).
Widget _validationFieldFillHeight(Widget field) {
return LayoutBuilder(
builder: (context, c) {
if (!c.hasBoundedHeight || !c.maxHeight.isFinite) return field;
return SizedBox(
height: c.maxHeight,
width: double.infinity,
child: field,
);
},
);
}
/// E-mail style validation — même supervision que login / création de compte : /// E-mail style validation — même supervision que login / création de compte :
/// [EmailMaxLengthFormatter] + à la perte de focus : trim/minuscules + validation. /// [EmailMaxLengthFormatter] + à la perte de focus : trim/minuscules + validation.
class ValidationEmailField extends StatefulWidget { class ValidationEmailField extends StatefulWidget {
@@ -354,23 +450,27 @@ class _ValidationEmailFieldState extends State<ValidationEmailField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return _validationFieldFillHeight(
key: _fieldKey, TextFormField(
controller: widget.controller, key: _fieldKey,
focusNode: _focusNode, controller: widget.controller,
keyboardType: TextInputType.emailAddress, focusNode: _focusNode,
autocorrect: false, keyboardType: TextInputType.emailAddress,
enableSuggestions: false, autocorrect: false,
autofillHints: const [AutofillHints.email], enableSuggestions: false,
textInputAction: TextInputAction.next, autofillHints: const [AutofillHints.email],
inputFormatters: const [EmailMaxLengthFormatter()], textInputAction: TextInputAction.next,
style: const TextStyle(color: Colors.black87, fontSize: 14), textAlignVertical: TextAlignVertical.center,
decoration: ValidationFieldDecoration.input(hint: widget.hintText).copyWith( inputFormatters: const [EmailMaxLengthFormatter()],
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11), style: ValidationFormMetrics.fieldTextStyle,
errorMaxLines: 2, decoration:
ValidationFieldDecoration.input(hint: widget.hintText).copyWith(
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
errorMaxLines: 2,
),
validator: (value) =>
validateEmail(value, allowEmpty: widget.allowEmpty),
), ),
validator: (value) =>
validateEmail(value, allowEmpty: widget.allowEmpty),
); );
} }
} }
@@ -431,22 +531,25 @@ class _ValidationPostalCodeFieldState extends State<ValidationPostalCodeField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return _validationFieldFillHeight(
key: _fieldKey, TextFormField(
controller: widget.controller, key: _fieldKey,
focusNode: _focusNode, controller: widget.controller,
keyboardType: TextInputType.number, focusNode: _focusNode,
textInputAction: TextInputAction.next, keyboardType: TextInputType.number,
inputFormatters: kFrenchPostalCodeInputFormatters, textInputAction: TextInputAction.next,
style: const TextStyle(color: Colors.black87, fontSize: 14), textAlignVertical: TextAlignVertical.center,
decoration: ValidationFieldDecoration.input( inputFormatters: kFrenchPostalCodeInputFormatters,
hint: widget.hintText ?? '5 chiffres', style: ValidationFormMetrics.fieldTextStyle,
).copyWith( decoration: ValidationFieldDecoration.input(
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11), hint: widget.hintText ?? '5 chiffres',
errorMaxLines: 2, ).copyWith(
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
errorMaxLines: 2,
),
validator: (value) =>
validateFrenchPostalCode(value, allowEmpty: widget.allowEmpty),
), ),
validator: (value) =>
validateFrenchPostalCode(value, allowEmpty: widget.allowEmpty),
); );
} }
} }
@@ -506,20 +609,24 @@ class _ValidationPhoneFieldState extends State<ValidationPhoneField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return _validationFieldFillHeight(
key: _fieldKey, TextFormField(
controller: widget.controller, key: _fieldKey,
focusNode: _focusNode, controller: widget.controller,
keyboardType: TextInputType.phone, focusNode: _focusNode,
textInputAction: TextInputAction.next, keyboardType: TextInputType.phone,
inputFormatters: frenchPhoneInputFormatters, textInputAction: TextInputAction.next,
style: const TextStyle(color: Colors.black87, fontSize: 14), textAlignVertical: TextAlignVertical.center,
decoration: ValidationFieldDecoration.input(hint: widget.hintText).copyWith( inputFormatters: frenchPhoneInputFormatters,
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11), style: ValidationFormMetrics.fieldTextStyle,
errorMaxLines: 2, decoration:
ValidationFieldDecoration.input(hint: widget.hintText).copyWith(
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
errorMaxLines: 2,
),
validator: (value) =>
validateFrenchNationalPhone(value, allowEmpty: widget.allowEmpty),
), ),
validator: (value) =>
validateFrenchNationalPhone(value, allowEmpty: widget.allowEmpty),
); );
} }
} }
@@ -591,23 +698,26 @@ class _ValidationNirFieldState extends State<ValidationNirField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return _validationFieldFillHeight(
key: _fieldKey, TextFormField(
controller: widget.controller, key: _fieldKey,
focusNode: _focusNode, controller: widget.controller,
keyboardType: TextInputType.text, focusNode: _focusNode,
textInputAction: TextInputAction.next, keyboardType: TextInputType.text,
autovalidateMode: AutovalidateMode.onUserInteraction, textInputAction: TextInputAction.next,
inputFormatters: const [NirInputFormatter()], textAlignVertical: TextAlignVertical.center,
style: const TextStyle(color: Colors.black87, fontSize: 14), autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: ValidationFieldDecoration.input( inputFormatters: const [NirInputFormatter()],
hint: widget.hintText ?? '1 12 34 56 789 012 - 34', style: ValidationFormMetrics.fieldTextStyle,
).copyWith( decoration: ValidationFieldDecoration.input(
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11), hint: widget.hintText ?? '1 12 34 56 789 012 - 34',
errorMaxLines: 2, ).copyWith(
errorStyle: TextStyle(color: Colors.red.shade700, fontSize: 11),
errorMaxLines: 2,
),
onChanged: (_) => _fieldKey.currentState?.validate(),
validator: _validator,
), ),
onChanged: (_) => _fieldKey.currentState?.validate(),
validator: _validator,
); );
} }
} }
@@ -685,16 +795,19 @@ class _ValidationReadOnlyFieldState extends State<ValidationReadOnlyField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!widget.compact && widget.maxLines == 1) { if (!widget.compact && widget.maxLines == 1) {
return TextField( return _validationFieldFillHeight(
controller: _controller, TextField(
readOnly: true, controller: _controller,
enableInteractiveSelection: false, readOnly: true,
style: TextStyle( enableInteractiveSelection: false,
color: widget.error ? Colors.red.shade800 : Colors.black87, textAlignVertical: TextAlignVertical.center,
fontSize: 14, style: TextStyle(
fontWeight: widget.error ? FontWeight.w600 : null, color: widget.error ? Colors.red.shade800 : Colors.black87,
fontSize: ValidationFormMetrics.fieldTextFontSize,
fontWeight: widget.error ? FontWeight.w600 : null,
),
decoration: ValidationFieldDecoration.readOnly(error: widget.error),
), ),
decoration: ValidationFieldDecoration.readOnly(error: widget.error),
); );
} }
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/dossier_unifie.dart'; import 'package:p_tits_pas/models/dossier_unifie.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/widgets/admin/am_dossier_wizard.dart';
import 'package:p_tits_pas/widgets/admin/validation_am_wizard.dart'; import 'package:p_tits_pas/widgets/admin/validation_am_wizard.dart';
import 'package:p_tits_pas/widgets/admin/validation_family_wizard.dart'; import 'package:p_tits_pas/widgets/admin/validation_family_wizard.dart';
@@ -76,8 +77,13 @@ class _ValidationDossierModalState extends State<ValidationDossierModal> {
/// Largeur modale = 1,5 × 620. /// Largeur modale = 1,5 × 620.
static const double _modalWidth = 930; // 620 * 1.5 static const double _modalWidth = 930; // 620 * 1.5
// Hauteur uniforme (ajustée +5px pour éviter l'overflow des étapes parents sans scroll). static const double _familyBodyHeight = 435;
static const double _bodyHeight = 435;
double get _bodyHeight {
final d = _dossier;
if (d != null && d.isAm) return AmDossierWizard.shellBodyHeight;
return _familyBodyHeight;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -68,7 +68,9 @@ class IdentityValues {
/// Bloc identité : Nom/Prénom, Tél/Email, Adresse, CP/Ville. /// Bloc identité : Nom/Prénom, Tél/Email, Adresse, CP/Ville.
/// Grille partagée (création de compte, validation AM/famille, fiches admin, etc.). /// Grille partagée (création de compte, validation AM/famille, fiches admin, etc.).
class IdentityBlock extends StatelessWidget { final String? title; class IdentityBlock extends StatelessWidget {
final String? title;
final bool expandVertically;
final String? _nom; final String? _nom;
final String? _prenom; final String? _prenom;
@@ -92,6 +94,7 @@ class IdentityBlock extends StatelessWidget { final String? title;
const IdentityBlock.readOnly({ const IdentityBlock.readOnly({
super.key, super.key,
this.title, this.title,
this.expandVertically = false,
required String nom, required String nom,
required String prenom, required String prenom,
required String telephone, required String telephone,
@@ -117,6 +120,7 @@ class IdentityBlock extends StatelessWidget { final String? title;
const IdentityBlock.editable({ const IdentityBlock.editable({
super.key, super.key,
this.title, this.title,
this.expandVertically = false,
required TextEditingController nomController, required TextEditingController nomController,
required TextEditingController prenomController, required TextEditingController prenomController,
required TextEditingController telephoneController, required TextEditingController telephoneController,
@@ -143,11 +147,13 @@ class IdentityBlock extends StatelessWidget { final String? title;
factory IdentityBlock.readOnlyValues({ factory IdentityBlock.readOnlyValues({
Key? key, Key? key,
String? title, String? title,
bool expandVertically = false,
required IdentityValues values, required IdentityValues values,
}) { }) {
return IdentityBlock.readOnly( return IdentityBlock.readOnly(
key: key, key: key,
title: title, title: title,
expandVertically: expandVertically,
nom: values.nom, nom: values.nom,
prenom: values.prenom, prenom: values.prenom,
telephone: values.telephone, telephone: values.telephone,
@@ -164,10 +170,12 @@ class IdentityBlock extends StatelessWidget { final String? title;
Key? key, Key? key,
String? title, String? title,
String emptyLabel = 'Non défini', String emptyLabel = 'Non défini',
bool expandVertically = false,
}) { }) {
return IdentityBlock.readOnlyValues( return IdentityBlock.readOnlyValues(
key: key, key: key,
title: title, title: title,
expandVertically: expandVertically,
values: IdentityValues.fromUser(user, emptyLabel: emptyLabel), values: IdentityValues.fromUser(user, emptyLabel: emptyLabel),
); );
} }
@@ -197,6 +205,7 @@ class IdentityBlock extends StatelessWidget { final String? title;
title: title, title: title,
rowLayout: rowLayout, rowLayout: rowLayout,
rowFlex: rowFlex, rowFlex: rowFlex,
expandVertically: expandVertically,
fields: [ fields: [
ValidationLabeledField( ValidationLabeledField(
label: 'Nom', label: 'Nom',
@@ -243,6 +252,7 @@ class IdentityBlock extends StatelessWidget { final String? title;
title: title, title: title,
rowLayout: rowLayout, rowLayout: rowLayout,
rowFlex: rowFlex, rowFlex: rowFlex,
expandVertically: expandVertically,
fields: [ fields: [
ValidationLabeledField( ValidationLabeledField(
label: 'Nom', label: 'Nom',