diff --git a/frontend/lib/widgets/professional_info_form_screen.dart b/frontend/lib/widgets/professional_info_form_screen.dart index dbc19dd..7843863 100644 --- a/frontend/lib/widgets/professional_info_form_screen.dart +++ b/frontend/lib/widgets/professional_info_form_screen.dart @@ -5,9 +5,11 @@ 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 'app_custom_checkbox.dart'; import 'hover_relief_widget.dart'; +import 'custom_navigation_button.dart'; /// Données pour le formulaire d'informations professionnelles class ProfessionalInfoData { @@ -36,7 +38,9 @@ class ProfessionalInfoData { /// 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; @@ -47,6 +51,7 @@ class ProfessionalInfoFormScreen extends StatefulWidget { const ProfessionalInfoFormScreen({ super.key, + this.mode = DisplayMode.editable, required this.stepText, required this.title, required this.cardColor, @@ -163,16 +168,7 @@ class _ProfessionalInfoFormScreenState extends State @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!); - } + final config = DisplayConfig.fromContext(context, mode: widget.mode); return Scaffold( body: Stack( @@ -186,201 +182,410 @@ class _ProfessionalInfoFormScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(widget.stepText, style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), - const SizedBox(height: 10), + 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: 24, + fontSize: config.isMobile ? 18 : 24, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), - const SizedBox(height: 30), + SizedBox(height: config.isMobile ? 16 : 30), Container( - width: screenSize.width * 0.6, - padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 50), - constraints: const BoxConstraints(minHeight: 650), + width: config.isMobile ? screenSize.width * 0.9 : screenSize.width * 0.6, + padding: EdgeInsets.symmetric( + vertical: config.isMobile ? 20 : 50, + horizontal: config.isMobile ? 24 : 50, + ), decoration: BoxDecoration( - image: DecorationImage(image: AssetImage(widget.cardColor.path), fit: BoxFit.fill), + image: DecorationImage( + image: AssetImage( + config.isMobile + ? _getVerticalCardAsset() + : 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; - }, - ), - ), - ], - ), - ], - ), + child: config.isMobile + ? _buildMobileFields(context, config) + : _buildDesktopFields(context, config), ), ), + // Boutons mobile sous la carte + if (config.isMobile) ...[ + const SizedBox(height: 20), + _buildMobileButtons(context, config, screenSize), + const SizedBox(height: 10), + ], ], ), ), ), - // 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), + // 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', ), - 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', + ), + ), + ], + ], + ), + ); + } + + /// Layout DESKTOP : Photo à gauche, champs à droite + Widget _buildDesktopFields(BuildContext context, DisplayConfig config) { + 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, + ), + const SizedBox(height: 32), + _buildField( + config: config, + label: 'Pays de naissance', + controller: _birthCountryController, + hint: 'Votre pays de naissance', + validator: (v) => v!.isEmpty ? 'Pays requis' : null, + ), + const SizedBox(height: 32), + _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: 32), + _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: 32), + 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, ), ), - // 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', + ), + 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, + }) { + 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; + } + } }