petitspas/frontend/lib/widgets/form_field_wrapper.dart
Julien Martin b18d5c8a9e feat(frontend): Refonte infrastructure formulaires multi-modes
- Support des modes Desktop/Mobile et Édition/Lecture seule
- Refactoring des widgets de formulaire (PersonalInfo, ProfessionalInfo, Presentation, ChildCard)
- Mise à jour des écrans de récapitulatif (ParentStep5, AmStep4)
- Ajout de navigation (Précédent/Soumettre) sur mobile

Closes #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 14:51:33 +01:00

206 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../config/display_config.dart';
import 'custom_app_text_field.dart';
/// Widget générique pour afficher un champ de formulaire
/// S'adapte automatiquement selon le DisplayConfig (editable/readonly, mobile/desktop)
class FormFieldWrapper extends StatelessWidget {
/// Configuration d'affichage
final DisplayConfig config;
/// Label du champ
final String label;
/// Valeur actuelle
final String value;
/// Controller pour le mode éditable
final TextEditingController? controller;
/// Callback de changement (mode éditable)
final ValueChanged<String>? onChanged;
/// Hint du champ (mode éditable)
final String? hint;
/// Nombre de lignes (pour textarea)
final int? maxLines;
/// Type de clavier
final TextInputType? keyboardType;
/// Widget personnalisé à afficher (override le champ standard)
final Widget? customWidget;
const FormFieldWrapper({
super.key,
required this.config,
required this.label,
required this.value,
this.controller,
this.onChanged,
this.hint,
this.maxLines,
this.keyboardType,
this.customWidget,
});
@override
Widget build(BuildContext context) {
if (config.isReadonly) {
return _buildReadonlyField(context);
} else {
return _buildEditableField(context);
}
}
/// Construit un champ en mode lecture seule
Widget _buildReadonlyField(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: LayoutHelper.getSpacing(context,
mobileSpacing: 8.0,
desktopSpacing: 12.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Text(
label,
style: GoogleFonts.merienda(
fontSize: config.isMobile ? 14 : 16,
fontWeight: FontWeight.bold,
color: const Color(0xFF4A4A4A),
),
),
const SizedBox(height: 6),
// Valeur avec fond beige
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
image: const DecorationImage(
image: AssetImage('assets/images/bg_beige.png'),
fit: BoxFit.fill,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
value.isEmpty ? '-' : value,
style: GoogleFonts.merienda(
fontSize: config.isMobile ? 14 : 16,
color: const Color(0xFF2C2C2C),
),
),
),
],
),
);
}
/// Construit un champ en mode éditable
Widget _buildEditableField(BuildContext context) {
// Si un widget personnalisé est fourni, l'utiliser
if (customWidget != null) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: LayoutHelper.getSpacing(context,
mobileSpacing: 8.0,
desktopSpacing: 12.0,
),
),
child: customWidget,
);
}
// Sinon, utiliser le champ standard
return Padding(
padding: EdgeInsets.symmetric(
vertical: LayoutHelper.getSpacing(context,
mobileSpacing: 8.0,
desktopSpacing: 12.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Text(
label,
style: GoogleFonts.merienda(
fontSize: config.isMobile ? 14 : 16,
fontWeight: FontWeight.w600,
color: const Color(0xFF4A4A4A),
),
),
const SizedBox(height: 8),
// Champ de saisie
CustomAppTextField(
controller: controller!,
labelText: label,
hintText: hint ?? label,
keyboardType: keyboardType ?? TextInputType.text,
fieldWidth: double.infinity,
),
],
),
);
}
}
/// Widget générique pour afficher une ligne de champs
/// S'adapte automatiquement: horizontal sur desktop, vertical sur mobile
class FormFieldRow extends StatelessWidget {
/// Configuration d'affichage
final DisplayConfig config;
/// Liste des champs à afficher
final List<Widget> fields;
/// Espacement entre les champs
final double? spacing;
const FormFieldRow({
super.key,
required this.config,
required this.fields,
this.spacing,
});
@override
Widget build(BuildContext context) {
final effectiveSpacing = spacing ??
LayoutHelper.getSpacing(context,
mobileSpacing: 12.0,
desktopSpacing: 20.0,
);
if (config.isMobile) {
// Layout vertical sur mobile
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: fields,
);
} else {
// Layout horizontal sur desktop
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (int i = 0; i < fields.length; i++) ...[
Expanded(child: fields[i]),
if (i < fields.length - 1) SizedBox(width: effectiveSpacing),
],
],
);
}
}
}