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 '../config/display_config.dart'; import 'custom_app_text_field.dart'; import 'form_field_wrapper.dart'; import 'app_custom_checkbox.dart'; import 'hover_relief_widget.dart'; import 'custom_navigation_button.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 /// Supporte mode éditable et readonly, responsive mobile/desktop class ProfessionalInfoFormScreen extends StatefulWidget { final DisplayMode mode; final String stepText; final String title; final CardColorHorizontal cardColor; final ProfessionalInfoData? initialData; final String previousRoute; final Function(ProfessionalInfoData) onSubmit; final Future Function()? onPickPhoto; final bool embedContentOnly; final VoidCallback? onEdit; const ProfessionalInfoFormScreen({ super.key, this.mode = DisplayMode.editable, required this.stepText, required this.title, required this.cardColor, this.initialData, required this.previousRoute, required this.onSubmit, this.onPickPhoto, this.embedContentOnly = false, this.onEdit, }); @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 config = DisplayConfig.fromContext(context, mode: widget.mode); if (widget.embedContentOnly) { return _buildCard(context, config, screenSize); } 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: config.isMobile ? 13 : 16, color: Colors.black54, ), ), SizedBox(height: config.isMobile ? 6 : 10), Text( widget.title, style: GoogleFonts.merienda( fontSize: config.isMobile ? 18 : 24, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), SizedBox(height: config.isMobile ? 16 : 30), _buildCard(context, config, screenSize), // Boutons mobile sous la carte if (config.isMobile) ...[ const SizedBox(height: 20), _buildMobileButtons(context, config, screenSize), const SizedBox(height: 10), ], ], ), ), ), // Chevrons desktop uniquement if (!config.isMobile) ...[ // 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', ), ), ], ], ), ); } Widget _buildCard(BuildContext context, DisplayConfig config, Size screenSize) { // Si mode Readonly Desktop : Layout spécial "Vintage" horizontal if (config.isReadonly && !config.isMobile && widget.embedContentOnly) { return _buildReadonlyDesktopCard(context, config, screenSize); } // Si mode Readonly Mobile : Layout spécial "Vintage" vertical if (config.isReadonly && config.isMobile && widget.embedContentOnly) { return Padding( padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05), child: _buildMobileReadonlyCard(context, config, screenSize), ); } return Stack( clipBehavior: Clip.none, children: [ Container( width: config.isMobile ? screenSize.width * 0.9 : screenSize.width * 0.6, padding: EdgeInsets.symmetric( vertical: config.isMobile ? 20 : (config.isReadonly ? 30 : 50), horizontal: config.isMobile ? 24 : 50, ), decoration: BoxDecoration( image: DecorationImage( image: AssetImage( config.isMobile ? _getVerticalCardAsset() : widget.cardColor.path ), fit: BoxFit.fill, ), ), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ if (widget.embedContentOnly) ...[ Text( widget.title, style: GoogleFonts.merienda( fontSize: config.isMobile ? 18 : 22, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), const SizedBox(height: 20), ], config.isMobile ? _buildMobileFields(context, config) : _buildDesktopFields(context, config), ], ), ), ), if (config.isReadonly && widget.onEdit != null) Positioned( top: 10, right: 10, child: IconButton( icon: const Icon(Icons.edit, color: Colors.black54), onPressed: widget.onEdit, tooltip: 'Modifier', ), ), ], ); } /// Carte en mode readonly MOBILE avec hauteur adaptative Widget _buildMobileReadonlyCard(BuildContext context, DisplayConfig config, Size screenSize) { return Container( width: double.infinity, // Pas de height fixe padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 24.0), decoration: BoxDecoration( image: DecorationImage( image: AssetImage(_getVerticalCardAsset()), fit: BoxFit.fill, // Fill pour s'adapter ), borderRadius: BorderRadius.circular(15), ), child: Stack( children: [ Column( mainAxisSize: MainAxisSize.min, children: [ // Titre + Edit Button Row( children: [ Expanded( child: Text( widget.title, style: GoogleFonts.merienda( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), ), if (widget.onEdit != null) const SizedBox(width: 28), ], ), // Contenu aligné en haut Padding( padding: const EdgeInsets.only(top: 20.0), child: _buildMobileFields(context, config), ), ], ), if (widget.onEdit != null) Positioned( top: 0, right: 0, child: IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(), icon: const Icon(Icons.edit, color: Colors.black54, size: 24), onPressed: widget.onEdit, tooltip: 'Modifier', ), ), ], ), ); } /// Carte en mode readonly desktop avec AspectRatio 2:1 Widget _buildReadonlyDesktopCard(BuildContext context, DisplayConfig config, Size screenSize) { final cardWidth = screenSize.width / 2.0; return SizedBox( width: cardWidth, child: AspectRatio( aspectRatio: 2.0, child: Container( padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 25.0), decoration: BoxDecoration( image: DecorationImage( image: AssetImage(widget.cardColor.path), fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(15), ), child: Column( children: [ // Titre + Edit Button Row( children: [ Expanded( child: Text( widget.title, style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center, ), ), if (widget.onEdit != null) IconButton( icon: const Icon(Icons.edit, color: Colors.black54, size: 28), onPressed: widget.onEdit, tooltip: 'Modifier', ), ], ), const SizedBox(height: 18), // Contenu Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // PHOTO (1/3) Expanded( flex: 1, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ AspectRatio( aspectRatio: 1, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(18), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 5), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(18), child: _photoFile != null ? Image.file(_photoFile!, fit: BoxFit.cover) : (_photoPathFramework != null && _photoPathFramework!.startsWith('assets/') ? Image.asset(_photoPathFramework!, fit: BoxFit.contain) : Image.asset('assets/images/photo.png', fit: BoxFit.contain)), ), ), ), const SizedBox(height: 10), AppCustomCheckbox( label: 'J\'accepte l\'utilisation\nde ma photo.', value: _photoConsent, onChanged: (v) {}, // Readonly checkboxSize: 22.0, fontSize: 14.0, ), ], ), ), const SizedBox(width: 32), // CHAMPS (2/3) - Layout optimisé compact Expanded( flex: 2, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Ligne 1 : Ville + Pays Row( children: [ Expanded(child: _buildReadonlyField('Ville de naissance', _birthCityController.text)), const SizedBox(width: 16), Expanded(child: _buildReadonlyField('Pays de naissance', _birthCountryController.text)), ], ), const SizedBox(height: 12), // Ligne 2 : Date + NIR (NIR prend plus de place si possible ou 50/50) Row( children: [ Expanded(flex: 2, child: _buildReadonlyField('Date de naissance', _dateOfBirthController.text)), const SizedBox(width: 16), Expanded(flex: 3, child: _buildReadonlyField('NIR', _nirController.text)), ], ), const SizedBox(height: 12), // Ligne 3 : Agrément + Capacité Row( children: [ Expanded(flex: 3, child: _buildReadonlyField('N° Agrément', _agrementController.text)), const SizedBox(width: 16), Expanded(flex: 2, child: _buildReadonlyField('Capacité', _capacityController.text)), ], ), ], ), ), ], ), ), ], ), ), ), ); } /// Helper pour champ Readonly style "Beige" Widget _buildReadonlyField(String label, String value) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: GoogleFonts.merienda(fontSize: 18.0, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Container( width: double.infinity, height: 45.0, // Hauteur réduite pour compacter padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), decoration: BoxDecoration( image: const DecorationImage( image: AssetImage('assets/images/bg_beige.png'), fit: BoxFit.fill, ), borderRadius: BorderRadius.circular(8), ), child: Text( value.isNotEmpty ? value : '-', style: GoogleFonts.merienda(fontSize: 16.0), overflow: TextOverflow.ellipsis, ), ), ], ); } /// Layout DESKTOP : Photo à gauche, champs à droite Widget _buildDesktopFields(BuildContext context, DisplayConfig config) { final double verticalSpacing = config.isReadonly ? 16.0 : 32.0; return Column( mainAxisSize: MainAxisSize.min, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Photo + Checkbox à gauche SizedBox( width: 300, child: _buildPhotoSection(context, config), ), const SizedBox(width: 30), // Champs à droite Expanded( child: Column( children: [ _buildField( config: config, label: 'Ville de naissance', controller: _birthCityController, hint: 'Votre ville de naissance', validator: (v) => v!.isEmpty ? 'Ville requise' : null, ), SizedBox(height: verticalSpacing), _buildField( config: config, label: 'Pays de naissance', controller: _birthCountryController, hint: 'Votre pays de naissance', validator: (v) => v!.isEmpty ? 'Pays requis' : null, ), SizedBox(height: verticalSpacing), _buildField( config: config, label: 'Date de naissance', controller: _dateOfBirthController, hint: 'JJ/MM/AAAA', readOnly: true, onTap: () => _selectDate(context), suffixIcon: Icons.calendar_today, validator: (v) => _selectedDate == null ? 'Date requise' : null, ), ], ), ), ], ), SizedBox(height: verticalSpacing), _buildField( config: config, label: 'N° Sécurité Sociale (NIR)', controller: _nirController, hint: 'Votre NIR à 13 chiffres', keyboardType: TextInputType.number, 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; }, ), SizedBox(height: verticalSpacing), Row( children: [ Expanded( child: _buildField( config: config, label: 'N° d\'agrément', controller: _agrementController, hint: 'Votre numéro d\'agrément', validator: (v) => v!.isEmpty ? 'Agrément requis' : null, ), ), const SizedBox(width: 20), Expanded( child: _buildField( config: config, label: 'Capacité d\'accueil', controller: _capacityController, hint: 'Ex: 3', keyboardType: TextInputType.number, 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; }, ), ), ], ), ], ); } /// Layout MOBILE : Tout empilé verticalement Widget _buildMobileFields(BuildContext context, DisplayConfig config) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Photo + Checkbox en premier _buildPhotoSection(context, config), const SizedBox(height: 20), _buildField( config: config, label: 'Ville de naissance', controller: _birthCityController, hint: 'Votre ville de naissance', validator: (v) => v!.isEmpty ? 'Ville requise' : null, ), const SizedBox(height: 12), _buildField( config: config, label: 'Pays de naissance', controller: _birthCountryController, hint: 'Votre pays de naissance', validator: (v) => v!.isEmpty ? 'Pays requis' : null, ), const SizedBox(height: 12), _buildField( config: config, label: 'Date de naissance', controller: _dateOfBirthController, hint: 'JJ/MM/AAAA', readOnly: true, onTap: () => _selectDate(context), suffixIcon: Icons.calendar_today, validator: (v) => _selectedDate == null ? 'Date requise' : null, ), const SizedBox(height: 12), _buildField( config: config, label: 'N° Sécurité Sociale (NIR)', controller: _nirController, hint: 'Votre NIR à 13 chiffres', keyboardType: TextInputType.number, 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: 12), _buildField( config: config, label: 'N° d\'agrément', controller: _agrementController, hint: 'Votre numéro d\'agrément', validator: (v) => v!.isEmpty ? 'Agrément requis' : null, ), const SizedBox(height: 12), _buildField( config: config, label: 'Capacité d\'accueil', controller: _capacityController, hint: 'Ex: 3', keyboardType: TextInputType.number, 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; }, ), ], ); } /// Section photo + checkbox Widget _buildPhotoSection(BuildContext context, DisplayConfig config) { 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!); } final photoSize = config.isMobile ? 200.0 : 270.0; return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ HoverReliefWidget( onPressed: _pickPhoto, borderRadius: BorderRadius.circular(10.0), initialShadowColor: initialPhotoShadow, hoverShadowColor: hoverPhotoShadow, child: SizedBox( height: photoSize, width: photoSize, 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), ), ], ); } /// Construit un champ individuel Widget _buildField({ required DisplayConfig config, required String label, required TextEditingController controller, String? hint, TextInputType? keyboardType, bool readOnly = false, VoidCallback? onTap, IconData? suffixIcon, String? Function(String?)? validator, }) { if (config.isReadonly) { return FormFieldWrapper( config: config, label: label, value: controller.text, ); } else { return CustomAppTextField( controller: controller, labelText: label, hintText: hint ?? label, fieldWidth: double.infinity, fieldHeight: config.isMobile ? 45.0 : 53.0, labelFontSize: config.isMobile ? 15.0 : 22.0, inputFontSize: config.isMobile ? 14.0 : 20.0, keyboardType: keyboardType ?? TextInputType.text, readOnly: readOnly, onTap: onTap, suffixIcon: suffixIcon, validator: validator, ); } } /// Boutons mobile Widget _buildMobileButtons(BuildContext context, DisplayConfig config, Size screenSize) { return Padding( padding: EdgeInsets.symmetric( horizontal: screenSize.width * 0.05, ), child: Row( children: [ Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Précédent', style: NavigationButtonStyle.purple, onPressed: () { if (context.canPop()) { context.pop(); } else { context.go(widget.previousRoute); } }, width: double.infinity, height: 50, fontSize: 16, ), ), ), const SizedBox(width: 16), Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Suivant', style: NavigationButtonStyle.green, onPressed: _submitForm, width: double.infinity, height: 50, fontSize: 16, ), ), ), ], ), ); } /// Retourne l'asset de carte vertical correspondant à la couleur String _getVerticalCardAsset() { switch (widget.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; } } }