From 96794919a8164fc9450730d17a28a8e336b145d2 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Wed, 28 Jan 2026 17:09:41 +0100 Subject: [PATCH] =?UTF-8?q?refactor(widgets):=20Extraire=20ProfessionalInf?= =?UTF-8?q?oFormScreen=20en=20widget=20r=C3=A9utilisable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouveau widget professional_info_form_screen.dart : - Formulaire complet d'infos professionnelles pour AM - Gestion de la photo avec sélection et consentement - Champs : ville/pays/date de naissance, NIR, agrément, capacité - Validations intégrées (NIR 13 chiffres, capacité > 0, etc.) AM Step 2 refactorisé : - Utilise le nouveau ProfessionalInfoFormScreen - Code réduit de ~280 lignes à ~75 lignes - Logique de génération de données de test préservée - Préparé pour réutilisation dans les récapitulatifs Impact : -205 lignes de code --- .../auth/am_register_step2_screen.dart | 372 +++-------------- .../professional_info_form_screen.dart | 386 ++++++++++++++++++ 2 files changed, 441 insertions(+), 317 deletions(-) create mode 100644 frontend/lib/widgets/professional_info_form_screen.dart diff --git a/frontend/lib/screens/auth/am_register_step2_screen.dart b/frontend/lib/screens/auth/am_register_step2_screen.dart index 78f334d..1496c6f 100644 --- a/frontend/lib/screens/auth/am_register_step2_screen.dart +++ b/frontend/lib/screens/auth/am_register_step2_screen.dart @@ -2,16 +2,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'dart:math' as math; -import 'dart:io'; // Pour FileImage si _pickPhoto utilise un File +import 'dart:io'; import '../../models/am_registration_data.dart'; -import '../../widgets/custom_app_text_field.dart'; -import '../../widgets/app_custom_checkbox.dart'; // Import de la checkbox -import '../../widgets/hover_relief_widget.dart'; // Import du HoverReliefWidget import '../../models/card_assets.dart'; import '../../utils/data_generator.dart'; +import '../../widgets/professional_info_form_screen.dart'; class AmRegisterStep2Screen extends StatefulWidget { const AmRegisterStep2Screen({super.key}); @@ -21,85 +17,9 @@ class AmRegisterStep2Screen extends StatefulWidget { } class _AmRegisterStep2ScreenState extends State { - final _formKey = GlobalKey(); + String? _photoPathFramework; + File? _photoFile; - final _dateOfBirthController = TextEditingController(); - // final _placeOfBirthController = TextEditingController(); // Remplacé - final _birthCityController = TextEditingController(); // Nouveau - final _birthCountryController = TextEditingController(); // Nouveau - final _nirController = TextEditingController(); - final _agrementController = TextEditingController(); - final _capacityController = TextEditingController(); - DateTime? _selectedDate; - String? _photoPathFramework; // Pour stocker le chemin de la photo (Asset ou File path) - File? _photoFile; // Pour stocker le fichier image si sélectionné localement - bool _photoConsent = false; - - @override - void initState() { - super.initState(); - final data = Provider.of(context, listen: false); - _selectedDate = data.dateOfBirth; - _dateOfBirthController.text = data.dateOfBirth != null ? DateFormat('dd/MM/yyyy').format(data.dateOfBirth!) : ''; - _birthCityController.text = data.birthCity; - _birthCountryController.text = data.birthCountry; - _nirController.text = data.nir; - _agrementController.text = data.agrementNumber; - _capacityController.text = data.capacity?.toString() ?? ''; - - // Générer des données de test si les champs sont vides - if (data.dateOfBirth == null && data.nir.isEmpty) { - _selectedDate = DateTime(1985, 3, 15); - _dateOfBirthController.text = DateFormat('dd/MM/yyyy').format(_selectedDate!); - _birthCityController.text = DataGenerator.city(); - _birthCountryController.text = 'France'; - _nirController.text = '${DataGenerator.randomIntInRange(1, 3)}${DataGenerator.randomIntInRange(80, 96)}${DataGenerator.randomIntInRange(1, 13).toString().padLeft(2, '0')}${DataGenerator.randomIntInRange(1, 100).toString().padLeft(2, '0')}${DataGenerator.randomIntInRange(100, 1000).toString().padLeft(3, '0')}${DataGenerator.randomIntInRange(100, 1000).toString().padLeft(3, '0')}${DataGenerator.randomIntInRange(10, 100).toString().padLeft(2, '0')}'; - _agrementController.text = 'AM${DataGenerator.randomIntInRange(10000, 100000)}'; - _capacityController.text = DataGenerator.randomIntInRange(1, 5).toString(); - _photoPathFramework = 'assets/images/icon_assmat.png'; - _photoConsent = true; - } - - // Gérer la photo existante (pourrait être un path d'asset ou un path de fichier) - if (data.photoPath != null) { - if (data.photoPath!.startsWith('assets/')) { - _photoPathFramework = data.photoPath; - _photoFile = null; - } else { - _photoFile = File(data.photoPath!); - _photoPathFramework = data.photoPath; // ou _photoFile.path - } - } - _photoConsent = data.photoConsent; - } - - @override - void dispose() { - _dateOfBirthController.dispose(); - _birthCityController.dispose(); - _birthCountryController.dispose(); - _nirController.dispose(); - _agrementController.dispose(); - _capacityController.dispose(); - super.dispose(); - } - - Future _selectDate(BuildContext context) async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: _selectedDate ?? DateTime.now().subtract(const Duration(days: 365 * 25)), // Default à 25 ans si null - firstDate: DateTime(1920, 1), - lastDate: DateTime.now().subtract(const Duration(days: 365 * 18)), // Assurer un âge minimum de 18 ans - locale: const Locale('fr', 'FR'), - ); - if (picked != null && picked != _selectedDate) { - setState(() { - _selectedDate = picked; - _dateOfBirthController.text = DateFormat('dd/MM/yyyy').format(picked); - }); - } - } - Future _pickPhoto() async { // TODO: Remplacer par la vraie logique ImagePicker // final imagePicker = ImagePicker(); @@ -107,249 +27,67 @@ class _AmRegisterStep2ScreenState extends State { // if (pickedFile != null) { // setState(() { // _photoFile = File(pickedFile.path); - // _photoPathFramework = pickedFile.path; // pour la sauvegarde + // _photoPathFramework = pickedFile.path; // }); // } else { - // // Simuler la sélection d'un asset pour test si aucun fichier n'est choisi - setState(() { - _photoPathFramework = 'assets/images/icon_assmat.png'; // Simule une photo asset - _photoFile = null; // Assurez-vous que _photoFile est null si c'est un asset - }); + setState(() { + _photoPathFramework = 'assets/images/icon_assmat.png'; + _photoFile = null; + }); // } print("Photo sélectionnée: $_photoPathFramework"); } - void _submitForm() { - if (_formKey.currentState!.validate()) { - if (_photoPathFramework != null && !_photoConsent) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')), - ); - return; - } - - Provider.of(context, listen: false) - .updateProfessionalInfo( - photoPath: _photoPathFramework, // Sauvegarder le chemin (asset ou fichier) - photoConsent: _photoConsent, - dateOfBirth: _selectedDate, - birthCity: _birthCityController.text, - birthCountry: _birthCountryController.text, - nir: _nirController.text, - agrementNumber: _agrementController.text, - capacity: int.tryParse(_capacityController.text) - ); - context.go('/am-register-step3'); - } - } - @override Widget build(BuildContext context) { - final screenSize = MediaQuery.of(context).size; - const cardColor = CardColorHorizontal.green; // Couleur de la carte - final Color baseCardColorForShadow = Colors.green.shade300; - final Color initialPhotoShadow = baseCardColorForShadow.withAlpha(90); - final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130); + final registrationData = Provider.of(context, listen: false); + + // Préparer les données initiales + ProfessionalInfoData initialData = ProfessionalInfoData( + photoPath: registrationData.photoPath, + photoConsent: registrationData.photoConsent, + dateOfBirth: registrationData.dateOfBirth, + birthCity: registrationData.birthCity, + birthCountry: registrationData.birthCountry, + nir: registrationData.nir, + agrementNumber: registrationData.agrementNumber, + capacity: registrationData.capacity, + ); - ImageProvider? currentImageProvider; - if (_photoFile != null) { - currentImageProvider = FileImage(_photoFile!); - } else if (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')) { - currentImageProvider = AssetImage(_photoPathFramework!); + // Générer des données de test si les champs sont vides + if (registrationData.dateOfBirth == null && registrationData.nir.isEmpty) { + initialData = ProfessionalInfoData( + photoPath: 'assets/images/icon_assmat.png', + photoConsent: true, + dateOfBirth: DateTime(1985, 3, 15), + birthCity: DataGenerator.city(), + birthCountry: 'France', + nir: '${DataGenerator.randomIntInRange(1, 3)}${DataGenerator.randomIntInRange(80, 96)}${DataGenerator.randomIntInRange(1, 13).toString().padLeft(2, '0')}${DataGenerator.randomIntInRange(1, 100).toString().padLeft(2, '0')}${DataGenerator.randomIntInRange(100, 1000).toString().padLeft(3, '0')}${DataGenerator.randomIntInRange(100, 1000).toString().padLeft(3, '0')}${DataGenerator.randomIntInRange(10, 100).toString().padLeft(2, '0')}', + agrementNumber: 'AM${DataGenerator.randomIntInRange(10000, 100000)}', + capacity: DataGenerator.randomIntInRange(1, 5), + ); } - return Scaffold( - body: Stack( - children: [ - Positioned.fill( - child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat), - ), - Center( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 40.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Étape 2/4', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), - const SizedBox(height: 10), - Text( - 'Vos informations professionnelles', - style: GoogleFonts.merienda( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 30), - Container( - width: screenSize.width * 0.6, - padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 50), - constraints: const BoxConstraints(minHeight: 650), - decoration: BoxDecoration( - image: DecorationImage(image: AssetImage(cardColor.path), fit: BoxFit.fill), - ), - child: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Colonne Gauche: Photo et Checkbox - SizedBox( - width: 300, // Largeur fixe pour la colonne photo (200 * 1.5) - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, // Centrer les éléments horizontalement - children: [ - HoverReliefWidget( - onPressed: _pickPhoto, - borderRadius: BorderRadius.circular(10.0), - initialShadowColor: initialPhotoShadow, - hoverShadowColor: hoverPhotoShadow, - child: SizedBox( - height: 270, // (180 * 1.5) - width: 270, // (180 * 1.5) - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - image: currentImageProvider != null - ? DecorationImage(image: currentImageProvider, fit: BoxFit.cover) - : null, - ), - child: currentImageProvider == null - ? Image.asset('assets/images/photo.png', fit: BoxFit.contain) - : null, - ), - ), - ), - const SizedBox(height: 10), // Espace réduit - AppCustomCheckbox( - label: 'J\'accepte l\'utilisation\nde ma photo.', - value: _photoConsent, - onChanged: (val) => setState(() => _photoConsent = val ?? false), - ), - ], - ), - ), - const SizedBox(width: 30), // Augmenter l'espace entre les colonnes - // Colonne Droite: Champs de naissance - Expanded( - child: Column( - children: [ - CustomAppTextField( - controller: _birthCityController, - labelText: 'Ville de naissance', - hintText: 'Votre ville de naissance', - fieldWidth: double.infinity, - validator: (v) => v!.isEmpty ? 'Ville requise' : null, - ), - const SizedBox(height: 32), - CustomAppTextField( - controller: _birthCountryController, - labelText: 'Pays de naissance', - hintText: 'Votre pays de naissance', - fieldWidth: double.infinity, - validator: (v) => v!.isEmpty ? 'Pays requis' : null, - ), - const SizedBox(height: 32), - CustomAppTextField( - controller: _dateOfBirthController, - labelText: 'Date de naissance', - hintText: 'JJ/MM/AAAA', - readOnly: true, - onTap: () => _selectDate(context), - suffixIcon: Icons.calendar_today, // Assurez-vous que CustomAppTextField gère suffixIcon - fieldWidth: double.infinity, - validator: (v) => _selectedDate == null ? 'Date requise' : null, - ), - ], - ), - ), - ], - ), - const SizedBox(height: 32), - CustomAppTextField( - controller: _nirController, - labelText: 'N° Sécurité Sociale (NIR)', - hintText: 'Votre NIR à 13 chiffres', - keyboardType: TextInputType.number, - fieldWidth: double.infinity, - validator: (v) { // Validation plus précise du NIR - if (v == null || v.isEmpty) return 'NIR requis'; - if (v.length != 13) return 'Le NIR doit contenir 13 chiffres'; - if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3'; - // D'autres validations plus complexes (clé de contrôle) peuvent être ajoutées - return null; - }, - ), - const SizedBox(height: 32), - Row( - children: [ - Expanded( - child: CustomAppTextField( - controller: _agrementController, - labelText: 'N° d\'agrément', - hintText: 'Votre numéro d\'agrément', - fieldWidth: double.infinity, - validator: (v) => v!.isEmpty ? 'Agrément requis' : null, - ), - ), - const SizedBox(width: 20), - Expanded( - child: CustomAppTextField( - controller: _capacityController, - labelText: 'Capacité d\'accueil', - hintText: 'Ex: 3', - keyboardType: TextInputType.number, - fieldWidth: double.infinity, - validator: (v) { - if (v == null || v.isEmpty) return 'Capacité requise'; - final n = int.tryParse(v); - if (n == null || n <= 0) return 'Nombre invalide'; - return null; - }, - ), - ), - ], - ), - ], - ), - ), - ), - ], - ), - ), - ), - // Chevron Gauche (Retour) - Positioned( - top: screenSize.height / 2 - 20, - left: 40, - child: IconButton( - icon: Transform(alignment: Alignment.center, transform: Matrix4.rotationY(math.pi), child: Image.asset('assets/images/chevron_right.png', height: 40)), - onPressed: () { - if (context.canPop()) { - context.pop(); - } else { - context.go('/am-register-step1'); - } - }, - tooltip: 'Précédent', - ), - ), - // Chevron Droit (Suivant) - Positioned( - top: screenSize.height / 2 - 20, - right: 40, - child: IconButton( - icon: Image.asset('assets/images/chevron_right.png', height: 40), - onPressed: _submitForm, - tooltip: 'Suivant', - ), - ), - ], - ), + return ProfessionalInfoFormScreen( + stepText: 'Étape 2/4', + title: 'Vos informations professionnelles', + cardColor: CardColorHorizontal.green, + initialData: initialData, + previousRoute: '/am-register-step1', + onPickPhoto: _pickPhoto, + onSubmit: (data) { + registrationData.updateProfessionalInfo( + photoPath: _photoPathFramework ?? data.photoPath, + photoConsent: data.photoConsent, + dateOfBirth: data.dateOfBirth, + birthCity: data.birthCity, + birthCountry: data.birthCountry, + nir: data.nir, + agrementNumber: data.agrementNumber, + capacity: data.capacity, + ); + context.go('/am-register-step3'); + }, ); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/lib/widgets/professional_info_form_screen.dart b/frontend/lib/widgets/professional_info_form_screen.dart new file mode 100644 index 0000000..dbc19dd --- /dev/null +++ b/frontend/lib/widgets/professional_info_form_screen.dart @@ -0,0 +1,386 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'dart:math' as math; +import 'dart:io'; +import '../models/card_assets.dart'; +import 'custom_app_text_field.dart'; +import 'app_custom_checkbox.dart'; +import 'hover_relief_widget.dart'; + +/// Données pour le formulaire d'informations professionnelles +class ProfessionalInfoData { + final String? photoPath; + final File? photoFile; + final bool photoConsent; + final DateTime? dateOfBirth; + final String birthCity; + final String birthCountry; + final String nir; + final String agrementNumber; + final int? capacity; + + ProfessionalInfoData({ + this.photoPath, + this.photoFile, + this.photoConsent = false, + this.dateOfBirth, + this.birthCity = '', + this.birthCountry = '', + this.nir = '', + this.agrementNumber = '', + this.capacity, + }); +} + +/// Widget générique pour le formulaire d'informations professionnelles +/// Utilisé pour l'inscription des Assistantes Maternelles +class ProfessionalInfoFormScreen extends StatefulWidget { + final String stepText; + final String title; + final CardColorHorizontal cardColor; + final ProfessionalInfoData? initialData; + final String previousRoute; + final Function(ProfessionalInfoData) onSubmit; + final Future Function()? onPickPhoto; + + const ProfessionalInfoFormScreen({ + super.key, + required this.stepText, + required this.title, + required this.cardColor, + this.initialData, + required this.previousRoute, + required this.onSubmit, + this.onPickPhoto, + }); + + @override + State createState() => _ProfessionalInfoFormScreenState(); +} + +class _ProfessionalInfoFormScreenState extends State { + final _formKey = GlobalKey(); + + final _dateOfBirthController = TextEditingController(); + final _birthCityController = TextEditingController(); + final _birthCountryController = TextEditingController(); + final _nirController = TextEditingController(); + final _agrementController = TextEditingController(); + final _capacityController = TextEditingController(); + + DateTime? _selectedDate; + String? _photoPathFramework; + File? _photoFile; + bool _photoConsent = false; + + @override + void initState() { + super.initState(); + + final data = widget.initialData; + if (data != null) { + _selectedDate = data.dateOfBirth; + _dateOfBirthController.text = data.dateOfBirth != null + ? DateFormat('dd/MM/yyyy').format(data.dateOfBirth!) + : ''; + _birthCityController.text = data.birthCity; + _birthCountryController.text = data.birthCountry; + _nirController.text = data.nir; + _agrementController.text = data.agrementNumber; + _capacityController.text = data.capacity?.toString() ?? ''; + _photoPathFramework = data.photoPath; + _photoFile = data.photoFile; + _photoConsent = data.photoConsent; + } + } + + @override + void dispose() { + _dateOfBirthController.dispose(); + _birthCityController.dispose(); + _birthCountryController.dispose(); + _nirController.dispose(); + _agrementController.dispose(); + _capacityController.dispose(); + super.dispose(); + } + + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _selectedDate ?? DateTime.now().subtract(const Duration(days: 365 * 25)), + firstDate: DateTime(1920, 1), + lastDate: DateTime.now().subtract(const Duration(days: 365 * 18)), + locale: const Locale('fr', 'FR'), + ); + if (picked != null && picked != _selectedDate) { + setState(() { + _selectedDate = picked; + _dateOfBirthController.text = DateFormat('dd/MM/yyyy').format(picked); + }); + } + } + + Future _pickPhoto() async { + if (widget.onPickPhoto != null) { + await widget.onPickPhoto!(); + } else { + // Comportement par défaut : utiliser un asset de test + setState(() { + _photoPathFramework = 'assets/images/icon_assmat.png'; + _photoFile = null; + }); + } + } + + void _submitForm() { + if (_formKey.currentState!.validate()) { + if (_photoPathFramework != null && !_photoConsent) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Veuillez accepter le consentement photo pour continuer.')), + ); + return; + } + + final data = ProfessionalInfoData( + photoPath: _photoPathFramework, + photoFile: _photoFile, + photoConsent: _photoConsent, + dateOfBirth: _selectedDate, + birthCity: _birthCityController.text, + birthCountry: _birthCountryController.text, + nir: _nirController.text, + agrementNumber: _agrementController.text, + capacity: int.tryParse(_capacityController.text), + ); + + widget.onSubmit(data); + } + } + + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + final Color baseCardColorForShadow = Colors.green.shade300; + final Color initialPhotoShadow = baseCardColorForShadow.withAlpha(90); + final Color hoverPhotoShadow = baseCardColorForShadow.withAlpha(130); + + ImageProvider? currentImageProvider; + if (_photoFile != null) { + currentImageProvider = FileImage(_photoFile!); + } else if (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/')) { + currentImageProvider = AssetImage(_photoPathFramework!); + } + + return Scaffold( + body: Stack( + children: [ + Positioned.fill( + child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat), + ), + Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(widget.stepText, style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), + const SizedBox(height: 10), + Text( + widget.title, + style: GoogleFonts.merienda( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 30), + Container( + width: screenSize.width * 0.6, + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 50), + constraints: const BoxConstraints(minHeight: 650), + decoration: BoxDecoration( + image: DecorationImage(image: AssetImage(widget.cardColor.path), fit: BoxFit.fill), + ), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Colonne Gauche: Photo et Checkbox + SizedBox( + width: 300, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + HoverReliefWidget( + onPressed: _pickPhoto, + borderRadius: BorderRadius.circular(10.0), + initialShadowColor: initialPhotoShadow, + hoverShadowColor: hoverPhotoShadow, + child: SizedBox( + height: 270, + width: 270, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: currentImageProvider != null + ? DecorationImage(image: currentImageProvider, fit: BoxFit.cover) + : null, + ), + child: currentImageProvider == null + ? Image.asset('assets/images/photo.png', fit: BoxFit.contain) + : null, + ), + ), + ), + const SizedBox(height: 10), + AppCustomCheckbox( + label: 'J\'accepte l\'utilisation\nde ma photo.', + value: _photoConsent, + onChanged: (val) => setState(() => _photoConsent = val ?? false), + ), + ], + ), + ), + const SizedBox(width: 30), + // Colonne Droite: Champs de naissance + Expanded( + child: Column( + children: [ + CustomAppTextField( + controller: _birthCityController, + labelText: 'Ville de naissance', + hintText: 'Votre ville de naissance', + fieldWidth: double.infinity, + labelFontSize: 22.0, + inputFontSize: 20.0, + validator: (v) => v!.isEmpty ? 'Ville requise' : null, + ), + const SizedBox(height: 32), + CustomAppTextField( + controller: _birthCountryController, + labelText: 'Pays de naissance', + hintText: 'Votre pays de naissance', + fieldWidth: double.infinity, + labelFontSize: 22.0, + inputFontSize: 20.0, + validator: (v) => v!.isEmpty ? 'Pays requis' : null, + ), + const SizedBox(height: 32), + CustomAppTextField( + controller: _dateOfBirthController, + labelText: 'Date de naissance', + hintText: 'JJ/MM/AAAA', + readOnly: true, + onTap: () => _selectDate(context), + suffixIcon: Icons.calendar_today, + fieldWidth: double.infinity, + labelFontSize: 22.0, + inputFontSize: 20.0, + validator: (v) => _selectedDate == null ? 'Date requise' : null, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 32), + CustomAppTextField( + controller: _nirController, + labelText: 'N° Sécurité Sociale (NIR)', + hintText: 'Votre NIR à 13 chiffres', + keyboardType: TextInputType.number, + fieldWidth: double.infinity, + labelFontSize: 22.0, + inputFontSize: 20.0, + validator: (v) { + if (v == null || v.isEmpty) return 'NIR requis'; + if (v.length != 13) return 'Le NIR doit contenir 13 chiffres'; + if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3'; + return null; + }, + ), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: CustomAppTextField( + controller: _agrementController, + labelText: 'N° d\'agrément', + hintText: 'Votre numéro d\'agrément', + fieldWidth: double.infinity, + labelFontSize: 22.0, + inputFontSize: 20.0, + validator: (v) => v!.isEmpty ? 'Agrément requis' : null, + ), + ), + const SizedBox(width: 20), + Expanded( + child: CustomAppTextField( + controller: _capacityController, + labelText: 'Capacité d\'accueil', + hintText: 'Ex: 3', + keyboardType: TextInputType.number, + fieldWidth: double.infinity, + labelFontSize: 22.0, + inputFontSize: 20.0, + validator: (v) { + if (v == null || v.isEmpty) return 'Capacité requise'; + final n = int.tryParse(v); + if (n == null || n <= 0) return 'Nombre invalide'; + return null; + }, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + // Chevron Gauche (Retour) + Positioned( + top: screenSize.height / 2 - 20, + left: 40, + child: IconButton( + icon: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(math.pi), + child: Image.asset('assets/images/chevron_right.png', height: 40), + ), + onPressed: () { + if (context.canPop()) { + context.pop(); + } else { + context.go(widget.previousRoute); + } + }, + tooltip: 'Précédent', + ), + ), + // Chevron Droit (Suivant) + Positioned( + top: screenSize.height / 2 - 20, + right: 40, + child: IconButton( + icon: Image.asset('assets/images/chevron_right.png', height: 40), + onPressed: _submitForm, + tooltip: 'Suivant', + ), + ), + ], + ), + ); + } +}