import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'admin_detail_modal.dart'; /// 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. /// [rowFlex] : flex par index de ligne (optionnel). Ex. {3: [2, 5]} = 4e ligne : code postal étroit (2), ville large (5). class ValidationDetailSection extends StatelessWidget { /// Si null ou vide, pas de bandeau titre (gain de place vertical, ex. wizard AM). final String? title; final List fields; /// Nombre de champs par ligne (1 = plein largeur, 2 = deux côte à côte). Ex. [2, 2, 1, 2] pour identité. final List? rowLayout; /// Flex par ligne (index de ligne -> [flex1, flex2, ...]). Ex. {3: [2, 5]} pour Code postal | Ville. final Map>? rowFlex; const ValidationDetailSection({ super.key, this.title, required this.fields, this.rowLayout, this.rowFlex, }); @override Widget build(BuildContext context) { return ValidationFormGrid( title: title, rowLayout: rowLayout, rowFlex: rowFlex, fields: fields .map( (f) => ValidationLabeledField( label: f.label, field: ValidationReadOnlyField(value: f.value), ), ) .toList(), ); } } /// Grille label/champ réutilisable (validation, fiches admin). class ValidationFormGrid extends StatelessWidget { final String? title; final List fields; final List? rowLayout; final Map>? rowFlex; const ValidationFormGrid({ super.key, this.title, required this.fields, this.rowLayout, this.rowFlex, }); @override Widget build(BuildContext context) { final layout = rowLayout ?? List.filled(fields.length, 1); int index = 0; int rowIndex = 0; final rows = []; for (final count in layout) { if (index >= fields.length) break; final rowFields = fields.skip(index).take(count).toList(); index += count; if (rowFields.isEmpty) continue; final flexForRow = rowFlex?[rowIndex]; rowIndex++; if (count == 1) { rows.add(Padding( padding: const EdgeInsets.only(bottom: 12), child: rowFields.first, )); } else { rows.add(Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (int i = 0; i < rowFields.length; i++) ...[ if (i > 0) const SizedBox(width: 16), Expanded( flex: (flexForRow != null && i < flexForRow.length) ? flexForRow[i] : 1, child: rowFields[i], ), ], ], ), )); } } final showTitle = title != null && title!.trim().isNotEmpty; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ if (showTitle) ...[ Text( title!.trim(), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(height: 12), ], ...rows, ], ); } } /// Décoration commune lecture seule / édition (modales validation, fiches admin). class ValidationFieldDecoration { ValidationFieldDecoration._(); static InputDecoration input({String? hint}) { return InputDecoration( isDense: true, filled: true, fillColor: Colors.grey.shade50, hintText: hint, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(6), borderSide: BorderSide(color: Colors.grey.shade500), ), ); } static BoxDecoration container() { return BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(6), border: Border.all(color: Colors.grey.shade300), ); } } /// Libellé au-dessus d’un champ (même typo que [ValidationDetailSection]). class ValidationLabeledField extends StatelessWidget { final String label; final Widget field; const ValidationLabeledField({ super.key, required this.label, required this.field, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.grey.shade700, ), ), const SizedBox(height: 4), field, ], ); } } /// Champ texte éditable, même rendu que [ValidationReadOnlyField]. class ValidationEditableField extends StatelessWidget { final TextEditingController controller; final TextInputType keyboardType; final List? inputFormatters; final String? hintText; final int maxLines; const ValidationEditableField({ super.key, required this.controller, this.keyboardType = TextInputType.text, this.inputFormatters, this.hintText, this.maxLines = 1, }); @override Widget build(BuildContext context) { return TextField( controller: controller, keyboardType: keyboardType, inputFormatters: inputFormatters, maxLines: maxLines, style: const TextStyle(color: Colors.black87, fontSize: 14), decoration: ValidationFieldDecoration.input(hint: hintText), ); } } /// Grille label/champ éditable (délègue à [ValidationFormGrid]). class ValidationEditableSection extends StatelessWidget { final List fields; final List? rowLayout; final Map>? rowFlex; const ValidationEditableSection({ super.key, required this.fields, this.rowLayout, this.rowFlex, }); @override Widget build(BuildContext context) { return ValidationFormGrid( rowLayout: rowLayout, rowFlex: rowFlex, fields: fields, ); } } /// Champ texte en lecture seule, style formulaire (fond gris léger, bordure). class ValidationReadOnlyField extends StatelessWidget { final String value; final int? maxLines; const ValidationReadOnlyField({ super.key, required this.value, this.maxLines = 1, }); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: ValidationFieldDecoration.container(), child: Text( value, style: const TextStyle(color: Colors.black87, fontSize: 14), maxLines: maxLines, overflow: TextOverflow.ellipsis, ), ); } }