diff --git a/frontend/lib/models/am_registration_data.dart b/frontend/lib/models/am_registration_data.dart new file mode 100644 index 0000000..b660210 --- /dev/null +++ b/frontend/lib/models/am_registration_data.dart @@ -0,0 +1,136 @@ +import 'package:flutter/foundation.dart'; + +class AmRegistrationData extends ChangeNotifier { + // Step 1: Identity Info + String firstName = ''; + String lastName = ''; + String streetAddress = ''; // Nouveau pour N° et Rue + String postalCode = ''; // Nouveau + String city = ''; // Nouveau + String phone = ''; + String email = ''; + String password = ''; + // String? photoPath; // Déplacé ou géré à l'étape 2 + // bool photoConsent = false; // Déplacé ou géré à l'étape 2 + + // Step 2: Professional Info + String? photoPath; // Ajouté pour l'étape 2 + bool photoConsent = false; // Ajouté pour l'étape 2 + DateTime? dateOfBirth; + String birthCity = ''; // Nouveau + String birthCountry = ''; // Nouveau + // String placeOfBirth = ''; // Remplacé par birthCity et birthCountry + String nir = ''; // Numéro de Sécurité Sociale + String agrementNumber = ''; // Numéro d'agrément + int? capacity; // Number of children the AM can look after + + // Step 3: Presentation & CGU + String presentationText = ''; + bool cguAccepted = false; + + // --- Methods to update data and notify listeners --- + + void updateIdentityInfo({ + String? firstName, + String? lastName, + String? streetAddress, // Modifié + String? postalCode, // Nouveau + String? city, // Nouveau + String? phone, + String? email, + String? password, + }) { + this.firstName = firstName ?? this.firstName; + this.lastName = lastName ?? this.lastName; + this.streetAddress = streetAddress ?? this.streetAddress; // Modifié + this.postalCode = postalCode ?? this.postalCode; // Nouveau + this.city = city ?? this.city; // Nouveau + this.phone = phone ?? this.phone; + this.email = email ?? this.email; + this.password = password ?? this.password; + // if (photoPath != null || this.photoPath != null) { // Supprimé de l'étape 1 + // this.photoPath = photoPath; + // } + // this.photoConsent = photoConsent ?? this.photoConsent; // Supprimé de l'étape 1 + notifyListeners(); + } + + void updateProfessionalInfo({ + String? photoPath, + bool? photoConsent, + DateTime? dateOfBirth, + String? birthCity, // Nouveau + String? birthCountry, // Nouveau + // String? placeOfBirth, // Remplacé + String? nir, + String? agrementNumber, + int? capacity, + }) { + // Allow setting photoPath to null explicitly + if (photoPath != null || this.photoPath != null) { + this.photoPath = photoPath; + } + this.photoConsent = photoConsent ?? this.photoConsent; + this.dateOfBirth = dateOfBirth ?? this.dateOfBirth; + this.birthCity = birthCity ?? this.birthCity; // Nouveau + this.birthCountry = birthCountry ?? this.birthCountry; // Nouveau + // this.placeOfBirth = placeOfBirth ?? this.placeOfBirth; // Remplacé + this.nir = nir ?? this.nir; + this.agrementNumber = agrementNumber ?? this.agrementNumber; + this.capacity = capacity ?? this.capacity; + notifyListeners(); + } + + void updatePresentationAndCgu({ + String? presentationText, + bool? cguAccepted, + }) { + this.presentationText = presentationText ?? this.presentationText; + this.cguAccepted = cguAccepted ?? this.cguAccepted; + notifyListeners(); + } + + // --- Getters for validation or display --- + bool get isStep1Complete => + firstName.isNotEmpty && + lastName.isNotEmpty && + streetAddress.isNotEmpty && // Modifié + postalCode.isNotEmpty && // Nouveau + city.isNotEmpty && // Nouveau + phone.isNotEmpty && + email.isNotEmpty; + // password n'est pas requis à l'inscription (défini après validation par lien email) + + bool get isStep2Complete => + // photoConsent is mandatory if a photo is system-required, otherwise optional. + // For now, let's assume if photoPath is present, consent should ideally be true. + // Or, make consent always mandatory if photo section exists. + // Based on new mockup, photo is present, so consent might be implicitly or explicitly needed. + (photoPath != null ? photoConsent == true : true) && // Ajuster selon la logique de consentement désirée + dateOfBirth != null && + birthCity.isNotEmpty && + birthCountry.isNotEmpty && + nir.isNotEmpty && // Basic check, could add validation + agrementNumber.isNotEmpty && + capacity != null && capacity! > 0; + + bool get isStep3Complete => + // presentationText is optional as per CDC (message au gestionnaire) + cguAccepted; + + bool get isRegistrationComplete => + isStep1Complete && isStep2Complete && isStep3Complete; + + @override + String toString() { + return 'AmRegistrationData(' + 'firstName: $firstName, lastName: $lastName, ' + 'streetAddress: $streetAddress, postalCode: $postalCode, city: $city, ' + 'phone: $phone, email: $email, ' + // 'photoPath: $photoPath, photoConsent: $photoConsent, ' // Commenté car déplacé/modifié + 'dateOfBirth: $dateOfBirth, birthCity: $birthCity, birthCountry: $birthCountry, ' + 'nir: $nir, agrementNumber: $agrementNumber, capacity: $capacity, ' + 'photoPath (step2): $photoPath, photoConsent (step2): $photoConsent, ' + 'presentationText: $presentationText, cguAccepted: $cguAccepted)'; + } +} diff --git a/frontend/lib/screens/auth/am_register_step1_screen.dart b/frontend/lib/screens/auth/am_register_step1_screen.dart new file mode 100644 index 0000000..f28c83e --- /dev/null +++ b/frontend/lib/screens/auth/am_register_step1_screen.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; + +import '../../models/am_registration_data.dart'; +import '../../utils/data_generator.dart'; +import '../../widgets/personal_info_form_screen.dart'; +import '../../models/card_assets.dart'; + +class AmRegisterStep1Screen extends StatelessWidget { + const AmRegisterStep1Screen({super.key}); + + @override + Widget build(BuildContext context) { + final registrationData = Provider.of(context, listen: false); + + // Générer des données de test si vide + PersonalInfoData initialData; + if (registrationData.firstName.isEmpty) { + final genFirstName = DataGenerator.firstName(); + final genLastName = DataGenerator.lastName(); + initialData = PersonalInfoData( + firstName: genFirstName, + lastName: genLastName, + phone: DataGenerator.phone(), + email: DataGenerator.email(genFirstName, genLastName), + address: DataGenerator.address(), + postalCode: DataGenerator.postalCode(), + city: DataGenerator.city(), + ); + } else { + initialData = PersonalInfoData( + firstName: registrationData.firstName, + lastName: registrationData.lastName, + phone: registrationData.phone, + email: registrationData.email, + address: registrationData.streetAddress, + postalCode: registrationData.postalCode, + city: registrationData.city, + ); + } + + return PersonalInfoFormScreen( + stepText: 'Étape 1/4', + title: 'Vos informations personnelles', + cardColor: CardColorHorizontal.blue, + initialData: initialData, + previousRoute: '/register-choice', + onSubmit: (data, {hasSecondPerson, sameAddress}) { + registrationData.updatePersonalInfo( + firstName: data.firstName, + lastName: data.lastName, + phone: data.phone, + email: data.email, + streetAddress: data.address, + postalCode: data.postalCode, + city: data.city, + password: '', + photoConsent: false, + ); + context.go('/am-register-step2'); + }, + ); + } +} diff --git a/frontend/lib/screens/auth/am_register_step2_screen.dart b/frontend/lib/screens/auth/am_register_step2_screen.dart new file mode 100644 index 0000000..78f334d --- /dev/null +++ b/frontend/lib/screens/auth/am_register_step2_screen.dart @@ -0,0 +1,355 @@ +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 '../../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'; + +class AmRegisterStep2Screen extends StatefulWidget { + const AmRegisterStep2Screen({super.key}); + + @override + State createState() => _AmRegisterStep2ScreenState(); +} + +class _AmRegisterStep2ScreenState extends State { + final _formKey = GlobalKey(); + + 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(); + // final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery); + // if (pickedFile != null) { + // setState(() { + // _photoFile = File(pickedFile.path); + // _photoPathFramework = pickedFile.path; // pour la sauvegarde + // }); + // } 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 + }); + // } + 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); + + 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('É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', + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/auth/am_register_step3_screen.dart b/frontend/lib/screens/auth/am_register_step3_screen.dart new file mode 100644 index 0000000..1fff3cb --- /dev/null +++ b/frontend/lib/screens/auth/am_register_step3_screen.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; + +import '../../models/am_registration_data.dart'; +import '../../widgets/presentation_form_screen.dart'; +import '../../models/card_assets.dart'; + +class AmRegisterStep3Screen extends StatelessWidget { + const AmRegisterStep3Screen({super.key}); + + @override + Widget build(BuildContext context) { + final data = Provider.of(context, listen: false); + + // Générer un texte de test si vide + String initialText = data.presentationText; + bool initialCgu = data.cguAccepted; + + if (initialText.isEmpty) { + initialText = 'Disponible immédiatement, plus de 10 ans d\'expérience avec les tout-petits. Formation aux premiers secours à jour. Je dispose d\'un jardin sécurisé et d\'un espace de jeu adapté.'; + initialCgu = true; + } + + return PresentationFormScreen( + stepText: 'Étape 3/4', + title: 'Présentation et Conditions', + cardColor: CardColorHorizontal.peach, + textFieldHint: 'Ex: Disponible immédiatement, 10 ans d\'expérience, formation premiers secours...', + initialText: initialText, + initialCguAccepted: initialCgu, + previousRoute: '/am-register-step2', + onSubmit: (text, cguAccepted) { + data.updatePresentationAndCgu( + presentationText: text, + cguAccepted: cguAccepted, + ); + context.go('/am-register-step4'); + }, + ); + } +} diff --git a/frontend/lib/screens/auth/am_register_step4_screen.dart b/frontend/lib/screens/auth/am_register_step4_screen.dart new file mode 100644 index 0000000..8ed3b36 --- /dev/null +++ b/frontend/lib/screens/auth/am_register_step4_screen.dart @@ -0,0 +1,339 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../models/am_registration_data.dart'; +import '../../widgets/image_button.dart'; +import '../../models/card_assets.dart'; +import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; + +// Méthode helper pour afficher un champ de type "lecture seule" stylisé +Widget _buildDisplayFieldValue(BuildContext context, String label, String value, {bool multiLine = false, double fieldHeight = 50.0, double labelFontSize = 18.0}) { + const FontWeight labelFontWeight = FontWeight.w600; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: GoogleFonts.merienda(fontSize: labelFontSize, fontWeight: labelFontWeight)), + const SizedBox(height: 4), + Container( + width: double.infinity, + height: multiLine ? null : fieldHeight, + constraints: multiLine ? const BoxConstraints(minHeight: 50.0) : null, + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/input_field_bg.png'), + fit: BoxFit.fill, + ), + ), + child: Text( + value.isNotEmpty ? value : '-', + style: GoogleFonts.merienda(fontSize: labelFontSize), + maxLines: multiLine ? null : 1, + overflow: multiLine ? TextOverflow.visible : TextOverflow.ellipsis, + ), + ), + ], + ); +} + +class AmRegisterStep4Screen extends StatefulWidget { + const AmRegisterStep4Screen({super.key}); + + @override + _AmRegisterStep4ScreenState createState() => _AmRegisterStep4ScreenState(); +} + +class _AmRegisterStep4ScreenState extends State { + @override + Widget build(BuildContext context) { + final registrationData = Provider.of(context); + final screenSize = MediaQuery.of(context).size; + + return Scaffold( + body: Stack( + children: [ + Positioned.fill( + child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeatY), + ), + Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: screenSize.width / 4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Étape 4/4', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), + const SizedBox(height: 20), + Text('Récapitulatif de votre demande', style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center), + const SizedBox(height: 30), + + _buildPersonalInfoCard(context, registrationData), + const SizedBox(height: 20), + _buildProfessionalInfoCard(context, registrationData), + const SizedBox(height: 20), + _buildPresentationCard(context, registrationData), + const SizedBox(height: 40), + + ImageButton( + bg: 'assets/images/btn_green.png', + text: 'Soumettre ma demande', + textColor: const Color(0xFF2D6A4F), + width: 350, + height: 50, + fontSize: 18, + onPressed: () { + print("Données AM finales: ${registrationData.firstName} ${registrationData.lastName}"); + _showConfirmationModal(context); + }, + ), + ], + ), + ), + ), + ), + Positioned( + top: screenSize.height / 2 - 20, + left: 40, + child: IconButton( + icon: Transform.flip(flipX: true, child: Image.asset('assets/images/chevron_right.png', height: 40)), + onPressed: () { + if (context.canPop()) { + context.pop(); + } else { + context.go('/am-register-step3'); + } + }, + tooltip: 'Retour', + ), + ), + ], + ), + ); + } + + void _showConfirmationModal(BuildContext context) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: Text( + 'Demande enregistrée', + style: GoogleFonts.merienda(fontWeight: FontWeight.bold), + ), + content: Text( + 'Votre dossier a bien été pris en compte. Un gestionnaire le validera bientôt.', + style: GoogleFonts.merienda(fontSize: 14), + ), + actions: [ + TextButton( + child: Text('OK', style: GoogleFonts.merienda(fontWeight: FontWeight.bold)), + onPressed: () { + Navigator.of(dialogContext).pop(); + context.go('/login'); + }, + ), + ], + ); + }, + ); + } + + // Carte Informations personnelles + Widget _buildPersonalInfoCard(BuildContext context, AmRegistrationData data) { + const double verticalSpacing = 28.0; + const double labelFontSize = 22.0; + + List details = [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Nom:", data.lastName, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Prénom:", data.firstName, labelFontSize: labelFontSize)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Téléphone:", data.phone, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Email:", data.email, multiLine: true, labelFontSize: labelFontSize)), + ], + ), + const SizedBox(height: verticalSpacing), + _buildDisplayFieldValue(context, "Adresse:", "${data.streetAddress}\n${data.postalCode} ${data.city}".trim(), multiLine: true, fieldHeight: 80, labelFontSize: labelFontSize), + const SizedBox(height: verticalSpacing), + _buildDisplayFieldValue(context, "Consentement photo:", data.photoConsent ? "Oui" : "Non", labelFontSize: labelFontSize), + ]; + + return _SummaryCard( + backgroundImagePath: CardColorHorizontal.blue.path, + title: 'Informations personnelles', + content: details, + onEdit: () => context.go('/am-register-step1'), + ); + } + + // Carte Informations professionnelles + Widget _buildProfessionalInfoCard(BuildContext context, AmRegistrationData data) { + const double verticalSpacing = 28.0; + const double labelFontSize = 22.0; + + String formattedDate = '-'; + if (data.dateOfBirth != null) { + formattedDate = '${data.dateOfBirth!.day.toString().padLeft(2, '0')}/${data.dateOfBirth!.month.toString().padLeft(2, '0')}/${data.dateOfBirth!.year}'; + } + String birthPlace = '${data.birthCity}, ${data.birthCountry}'.trim(); + if (birthPlace == ',') birthPlace = '-'; + + List details = [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Date de naissance:", formattedDate, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Lieu de naissance:", birthPlace, labelFontSize: labelFontSize, multiLine: true)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "N° Sécurité Sociale:", data.nir, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "N° Agrément:", data.agrementNumber, labelFontSize: labelFontSize)), + ], + ), + const SizedBox(height: verticalSpacing), + _buildDisplayFieldValue(context, "Capacité d'accueil:", data.capacity?.toString() ?? '-', labelFontSize: labelFontSize), + ]; + + return _SummaryCard( + backgroundImagePath: CardColorHorizontal.green.path, + title: 'Informations professionnelles', + content: details, + onEdit: () => context.go('/am-register-step2'), + ); + } + + // Carte Présentation & CGU + Widget _buildPresentationCard(BuildContext context, AmRegistrationData data) { + const double labelFontSize = 22.0; + + List details = [ + Text( + 'Votre présentation (facultatif) :', + style: GoogleFonts.merienda(fontSize: labelFontSize, fontWeight: FontWeight.w600), + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + constraints: const BoxConstraints(minHeight: 80.0), + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/input_field_bg.png'), + fit: BoxFit.fill, + ), + ), + child: Text( + data.presentationText.isNotEmpty ? data.presentationText : 'Aucune présentation rédigée.', + style: GoogleFonts.merienda( + fontSize: 18, + fontStyle: data.presentationText.isNotEmpty ? FontStyle.normal : FontStyle.italic, + color: data.presentationText.isNotEmpty ? Colors.black87 : Colors.black54, + ), + ), + ), + const SizedBox(height: 20), + Row( + children: [ + Icon( + data.cguAccepted ? Icons.check_circle : Icons.cancel, + color: data.cguAccepted ? Colors.green : Colors.red, + size: 24, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + data.cguAccepted ? 'CGU acceptées' : 'CGU non acceptées', + style: GoogleFonts.merienda(fontSize: 18), + ), + ), + ], + ), + ]; + + return _SummaryCard( + backgroundImagePath: CardColorHorizontal.peach.path, + title: 'Présentation & CGU', + content: details, + onEdit: () => context.go('/am-register-step3'), + ); + } +} + +// Widget générique _SummaryCard +class _SummaryCard extends StatelessWidget { + final String backgroundImagePath; + final String title; + final List content; + final VoidCallback onEdit; + + const _SummaryCard({ + super.key, + required this.backgroundImagePath, + required this.title, + required this.content, + required this.onEdit, + }); + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 2.0, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 25.0), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(backgroundImagePath), + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.circular(15), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Align( + alignment: Alignment.center, + child: Text( + title, + style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), + ), + ), + const SizedBox(height: 12), + ...content, + ], + ), + ), + IconButton( + icon: const Icon(Icons.edit, color: Colors.black54, size: 28), + onPressed: onEdit, + tooltip: 'Modifier', + ), + ], + ), + ), + ); + } +} diff --git a/frontend/lib/screens/auth/register_choice_screen.dart b/frontend/lib/screens/auth/register_choice_screen.dart index bdf877e..e80238b 100644 --- a/frontend/lib/screens/auth/register_choice_screen.dart +++ b/frontend/lib/screens/auth/register_choice_screen.dart @@ -101,7 +101,7 @@ class RegisterChoiceScreen extends StatelessWidget { iconPath: 'assets/images/icon_assmat.png', label: 'Assistante Maternelle', onPressed: () { - context.go('/nanny-register-step1'); + context.go('/am-register-step1'); }, ), ],