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>
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../config/display_config.dart';
|
||||
import '../models/card_assets.dart';
|
||||
import 'hover_relief_widget.dart';
|
||||
import 'image_button.dart';
|
||||
|
||||
/// Widget de base générique pour tous les écrans de formulaire
|
||||
/// Gère automatiquement le layout, les boutons de navigation, etc.
|
||||
class BaseFormScreen extends StatelessWidget {
|
||||
/// Configuration d'affichage
|
||||
final DisplayConfig config;
|
||||
|
||||
/// Texte de l'étape (ex: "Étape 1/4")
|
||||
final String stepText;
|
||||
|
||||
/// Titre du formulaire
|
||||
final String title;
|
||||
|
||||
/// Couleur de la carte (horizontal pour desktop)
|
||||
final CardColorHorizontal cardColor;
|
||||
|
||||
/// Contenu du formulaire
|
||||
final Widget content;
|
||||
|
||||
/// Texte du bouton de soumission (par défaut "Suivant")
|
||||
final String? submitButtonText;
|
||||
|
||||
/// Callback de soumission
|
||||
final VoidCallback onSubmit;
|
||||
|
||||
/// Route précédente (pour le bouton retour)
|
||||
final String previousRoute;
|
||||
|
||||
/// Widget supplémentaire au-dessus du contenu (ex: toggle)
|
||||
final Widget? headerWidget;
|
||||
|
||||
/// Widget supplémentaire en dessous du contenu (ex: checkbox CGU)
|
||||
final Widget? footerWidget;
|
||||
|
||||
/// Padding personnalisé pour le contenu
|
||||
final EdgeInsets? contentPadding;
|
||||
|
||||
const BaseFormScreen({
|
||||
super.key,
|
||||
required this.config,
|
||||
required this.stepText,
|
||||
required this.title,
|
||||
required this.cardColor,
|
||||
required this.content,
|
||||
required this.onSubmit,
|
||||
required this.previousRoute,
|
||||
this.submitButtonText,
|
||||
this.headerWidget,
|
||||
this.footerWidget,
|
||||
this.contentPadding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFFFF8E1),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(
|
||||
LayoutHelper.getSpacing(context,
|
||||
mobileSpacing: 16.0,
|
||||
desktopSpacing: 32.0,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: LayoutHelper.getMaxWidth(context),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Texte de l'étape
|
||||
Text(
|
||||
stepText,
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: config.isMobile ? 14 : 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF6D4C41),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Titre
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: config.isMobile ? 24 : 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: const Color(0xFF4A4A4A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Header widget (si fourni)
|
||||
if (headerWidget != null) ...[
|
||||
headerWidget!,
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Carte principale
|
||||
_buildCard(context),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Footer widget (si fourni)
|
||||
if (footerWidget != null) ...[
|
||||
footerWidget!,
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
|
||||
// Boutons de navigation
|
||||
_buildNavigationButtons(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Construit la carte principale
|
||||
Widget _buildCard(BuildContext context) {
|
||||
final effectivePadding = contentPadding ??
|
||||
EdgeInsets.all(
|
||||
LayoutHelper.getSpacing(context,
|
||||
mobileSpacing: 16.0,
|
||||
desktopSpacing: 32.0,
|
||||
),
|
||||
);
|
||||
|
||||
if (config.isMobile) {
|
||||
// Carte verticale sur mobile
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(_getVerticalCardAsset()),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: effectivePadding,
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Carte horizontale sur desktop
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(cardColor.path),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: effectivePadding,
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Retourne l'asset de carte vertical correspondant à la couleur
|
||||
String _getVerticalCardAsset() {
|
||||
// Mapping couleur horizontale -> verticale
|
||||
switch (cardColor) {
|
||||
case CardColorHorizontal.blue:
|
||||
return CardColorVertical.blue.path;
|
||||
case CardColorHorizontal.green:
|
||||
return CardColorVertical.green.path;
|
||||
case CardColorHorizontal.lavender:
|
||||
return CardColorVertical.lavender.path;
|
||||
case CardColorHorizontal.lime:
|
||||
return CardColorVertical.lime.path;
|
||||
case CardColorHorizontal.peach:
|
||||
return CardColorVertical.peach.path;
|
||||
case CardColorHorizontal.pink:
|
||||
return CardColorVertical.pink.path;
|
||||
case CardColorHorizontal.red:
|
||||
return CardColorVertical.red.path;
|
||||
}
|
||||
}
|
||||
|
||||
/// Construit les boutons de navigation
|
||||
Widget _buildNavigationButtons(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// Bouton Précédent
|
||||
HoverReliefWidget(
|
||||
child: ImageButton(
|
||||
bg: 'assets/images/bg_green.png',
|
||||
text: 'Précédent',
|
||||
textColor: Colors.white,
|
||||
onPressed: () => Navigator.pushNamed(context, previousRoute),
|
||||
width: config.isMobile ? 120 : 150,
|
||||
height: config.isMobile ? 40 : 50,
|
||||
),
|
||||
),
|
||||
|
||||
// Bouton Suivant/Soumettre
|
||||
HoverReliefWidget(
|
||||
child: ImageButton(
|
||||
bg: 'assets/images/bg_green.png',
|
||||
text: submitButtonText ?? 'Suivant',
|
||||
textColor: Colors.white,
|
||||
onPressed: config.isReadonly ? onSubmit : () {
|
||||
// En mode éditable, valider avant de soumettre
|
||||
onSubmit();
|
||||
},
|
||||
width: config.isMobile ? 120 : 150,
|
||||
height: config.isMobile ? 40 : 50,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user