From 4042d5823ea98e5c15ed044bdec3c878caa6bbce Mon Sep 17 00:00:00 2001 From: Hanim Date: Tue, 12 Aug 2025 15:49:46 +0200 Subject: [PATCH 1/7] feat: Implement Parent Registration Step 4 and Step 5 Screens - Added ParentRegisterStep4Screen for capturing motivation and CGU acceptance. - Integrated custom checkbox and text field widgets for better UI. - Implemented modal dialog for displaying CGU text. - Created ParentRegisterStep5Screen for summarizing registration data. - Added functionality to display parent and child details with edit options. - Included confirmation modal upon submission of the registration request. --- frontend/CONTRIBUTING.md | 74 +++++++++++++++++++ frontend/README.md | 53 +++++++++++++ frontend/android/local.properties | 3 +- frontend/lib/navigation/app_router.dart | 10 +-- .../auth/am/am_register_step1_sceen.dart | 18 +++++ .../parent_register_step1_screen.dart | 8 +- .../parent_register_step2_screen.dart | 8 +- .../parent_register_step3_screen.dart | 12 +-- .../parent_register_step4_screen.dart | 6 +- .../parent_register_step5_screen.dart | 8 +- 10 files changed, 173 insertions(+), 27 deletions(-) create mode 100644 frontend/CONTRIBUTING.md create mode 100644 frontend/lib/screens/auth/am/am_register_step1_sceen.dart rename frontend/lib/screens/auth/{ => parent}/parent_register_step1_screen.dart (96%) rename frontend/lib/screens/auth/{ => parent}/parent_register_step2_screen.dart (97%) rename frontend/lib/screens/auth/{ => parent}/parent_register_step3_screen.dart (97%) rename frontend/lib/screens/auth/{ => parent}/parent_register_step4_screen.dart (98%) rename frontend/lib/screens/auth/{ => parent}/parent_register_step5_screen.dart (98%) diff --git a/frontend/CONTRIBUTING.md b/frontend/CONTRIBUTING.md new file mode 100644 index 0000000..f61eefb --- /dev/null +++ b/frontend/CONTRIBUTING.md @@ -0,0 +1,74 @@ +# 🚀 Guide de contribution – Projet P'titsPas (Flutter) + +Bienvenue ! Ce guide explique comment collaborer efficacement sur ce projet Flutter. + +--- + +## 📌 Branches Git + +Le projet suit une stratégie de branches simple et efficace : + +| Branche | Rôle | +|---------------|---------------------------------------------| +| `main` | Production (version stable déployée) | +| `develop` | Intégration (version en cours de test) | +| `feature/XXX` | Développement d’une nouvelle fonctionnalité | +| `fix/XXX` | Correction de bug | +| `hotfix/XXX` | Patch urgent sur `main` | + +--- + +## ✅ Cycle de développement + +1. **Crée une branche à partir de `develop`** + ```bash + git checkout develop + git pull origin develop + git checkout -b feature/FRONT-XXX-nom-fonctionnalite + +2. Travaille localement + commit régulièrement + + Commits clairs et concis : + Nom de la branche: Fonctionnalité push + + ```bash + git commit -m "FRONT-021: ajout du widget zone enfants" + ``` + +3. Pousse ta branche + + Exemple + ```bash + git push origin feature/FRONT-XXX-nom + + +4. Ouvre une Pull Request vers develop + + Ps : **La PR vers develop est faite lorsque une fonctionnalité du ticket à été fait et testé ou lorsque tous le ticket est finis** + - Titre : [FRONT-021] Widget zone enfants + + - Description : ce que tu as fait, ce qu’il reste à tester + - Lie le ticket associé (ex: Fixes #21) + +5. Relecture & Merge + - Au moins 1 review nécessaire + - Pas de commit direct sur develop ou main + +6. Une fois merge, supprime la branche distante: + + PS: **La branche est supprimé que lorsque tout le ticket a été consommé** + ```bash + git push origin --delete feature/FRONT-XXX-nom + ``` + +🧼 Règles de bonne conduite + +- Une PR = une seule fonctionnalité ou correction + +- Code commenté si logique complexe + +- Garder les noms de variables/dossiers clairs et en anglais + +- Pas de code mort ou non utilisé + +- Tester les commandes du workflow(dans le .github) afin d'être sur de ne pas avoir des erreur dans le code et pour etre sur de passer les tests du Workflow \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 9e6f97c..1151cd8 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -14,3 +14,56 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +### Workflow Git + +Le projet suit un **Git Flow simplifié** avec 3 branches principales : + +- `main` : version stable et déployée en production +- `develop` : version intégrée et testée avant passage en production +- `feature/*`, `fix/*`, `hotfix/*` : branches spécifiques au développement + +**Cycle standard :** +```bash +# Création d’une feature +git checkout develop +git checkout -b feature/FRONT-021-zone-enfants + +# Développement +git add . +git commit -m "FRONT-021: Widget zone enfants" +git push origin feature/FRONT-021-zone-enfants + +# Pull Request => vers develop +# Merge → suppression de la branche +Voir CONTRIBUTING.md pour les conventions détaillées. +``` +### Structure du projet Flutter + +Le projet suit une architecture modulaire MVC simplifiée compatible avec Provider (ou Riverpod léger). + +```plaintext +lib/ +├── main.dart # Point d’entrée +├── routes/ # go_router ou auto_route +├── models/ # Classes de données (User, Parent, Enfant, etc.) +├── services/ # Requêtes HTTP, AuthService, StorageService +├── utils/ # Helpers, validateurs, formatteurs +├── widgets/ # Composants UI réutilisables +├── screens/ # Pages par grande fonctionnalité +│ ├── auth/ # Connexion, inscription, mot de passe oublié +│ ├── registration/ # Création parent / assistante maternelle +│ ├── dashboard/ # Tableau de bord parent / AM / gestionnaire +│ ├── profile/ # Gestion des infos utilisateur +│ └── children/ # Fiches enfants +``` + +### Architecture choisie +🟩 Type : MVC Modulaire avec Provider (ou Riverpod léger) + +Avantages : + +- Simple à prendre en main +- Rapide à structurer +- Permet la séparation des features +- Adaptée à un projet Flutter Web PWA diff --git a/frontend/android/local.properties b/frontend/android/local.properties index 189af7a..0290669 100644 --- a/frontend/android/local.properties +++ b/frontend/android/local.properties @@ -1 +1,2 @@ -flutter.sdk=C:\\Users\\marti\\dev\\flutter \ No newline at end of file +flutter.sdk=C:\\Users\\myhan\\flutter +sdk.dir=C:\\Users\\myhan\\AppData\\Local\\Android\\Sdk \ No newline at end of file diff --git a/frontend/lib/navigation/app_router.dart b/frontend/lib/navigation/app_router.dart index 70b55a2..397113d 100644 --- a/frontend/lib/navigation/app_router.dart +++ b/frontend/lib/navigation/app_router.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../screens/auth/login_screen.dart'; import '../screens/auth/register_choice_screen.dart'; -import '../screens/auth/parent_register_step1_screen.dart'; -import '../screens/auth/parent_register_step2_screen.dart'; -import '../screens/auth/parent_register_step3_screen.dart'; -import '../screens/auth/parent_register_step4_screen.dart'; -import '../screens/auth/parent_register_step5_screen.dart'; +import '../screens/auth/parent/parent_register_step1_screen.dart'; +import '../screens/auth/parent/parent_register_step2_screen.dart'; +import '../screens/auth/parent/parent_register_step3_screen.dart'; +import '../screens/auth/parent/parent_register_step4_screen.dart'; +import '../screens/auth/parent/parent_register_step5_screen.dart'; import '../screens/home/home_screen.dart'; import '../models/user_registration_data.dart'; diff --git a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart new file mode 100644 index 0000000..9fb1909 --- /dev/null +++ b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart @@ -0,0 +1,18 @@ + +import 'package:flutter/material.dart'; + +class AmRegisterStep1Screen extends StatelessWidget { + const AmRegisterStep1Screen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Étape 1 - Inscription AM'), + ), + body: const Center( + child: Text('Contenu de l\'étape 1'), + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/auth/parent_register_step1_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart similarity index 96% rename from frontend/lib/screens/auth/parent_register_step1_screen.dart rename to frontend/lib/screens/auth/parent/parent_register_step1_screen.dart index 0463967..6f04062 100644 --- a/frontend/lib/screens/auth/parent_register_step1_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron -import '../../models/user_registration_data.dart'; // Import du modèle de données -import '../../utils/data_generator.dart'; // Import du générateur de données -import '../../widgets/custom_app_text_field.dart'; // Import du widget CustomAppTextField -import '../../models/card_assets.dart'; // Import des enums de cartes +import '../../../models/user_registration_data.dart'; // Import du modèle de données +import '../../../utils/data_generator.dart'; // Import du générateur de données +import '../../../widgets/custom_app_text_field.dart'; // Import du widget CustomAppTextField +import '../../../models/card_assets.dart'; // Import des enums de cartes class ParentRegisterStep1Screen extends StatefulWidget { const ParentRegisterStep1Screen({super.key}); diff --git a/frontend/lib/screens/auth/parent_register_step2_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart similarity index 97% rename from frontend/lib/screens/auth/parent_register_step2_screen.dart rename to frontend/lib/screens/auth/parent/parent_register_step2_screen.dart index a7ecaf2..f1008c0 100644 --- a/frontend/lib/screens/auth/parent_register_step2_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron -import '../../models/user_registration_data.dart'; // Import du modèle -import '../../utils/data_generator.dart'; // Import du générateur -import '../../widgets/custom_app_text_field.dart'; // Import du widget -import '../../models/card_assets.dart'; // Import des enums de cartes +import '../../../models/user_registration_data.dart'; // Import du modèle +import '../../../utils/data_generator.dart'; // Import du générateur +import '../../../widgets/custom_app_text_field.dart'; // Import du widget +import '../../../models/card_assets.dart'; // Import des enums de cartes class ParentRegisterStep2Screen extends StatefulWidget { final UserRegistrationData registrationData; // Accepte les données de l'étape 1 diff --git a/frontend/lib/screens/auth/parent_register_step3_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart similarity index 97% rename from frontend/lib/screens/auth/parent_register_step3_screen.dart rename to frontend/lib/screens/auth/parent/parent_register_step3_screen.dart index ac9daff..4797050 100644 --- a/frontend/lib/screens/auth/parent_register_step3_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart @@ -2,16 +2,16 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron import 'package:flutter/gestures.dart'; // Pour PointerDeviceKind -import '../../widgets/hover_relief_widget.dart'; // Import du nouveau widget +import '../../../widgets/hover_relief_widget.dart'; // Import du nouveau widget import 'package:image_picker/image_picker.dart'; // import 'package:image_cropper/image_cropper.dart'; // Supprimé import 'dart:io' show File, Platform; // Ajout de Platform import 'package:flutter/foundation.dart' show kIsWeb; // Import pour kIsWeb -import '../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField -import '../../widgets/app_custom_checkbox.dart'; // Import du nouveau widget Checkbox -import '../../models/user_registration_data.dart'; // Import du modèle de données -import '../../utils/data_generator.dart'; // Import du générateur -import '../../models/card_assets.dart'; // Import des enums de cartes +import '../../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField +import '../../../widgets/app_custom_checkbox.dart'; // Import du nouveau widget Checkbox +import '../../../models/user_registration_data.dart'; // Import du modèle de données +import '../../../utils/data_generator.dart'; // Import du générateur +import '../../../models/card_assets.dart'; // Import des enums de cartes // La classe _ChildFormData est supprimée car on utilise ChildData du modèle diff --git a/frontend/lib/screens/auth/parent_register_step4_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart similarity index 98% rename from frontend/lib/screens/auth/parent_register_step4_screen.dart rename to frontend/lib/screens/auth/parent/parent_register_step4_screen.dart index 62ae003..c4a009e 100644 --- a/frontend/lib/screens/auth/parent_register_step4_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart @@ -4,9 +4,9 @@ import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart'; // Import import 'dart:math' as math; // Pour la rotation du chevron import 'package:p_tits_pas/widgets/app_custom_checkbox.dart'; // Import de la checkbox personnalisée // import 'package:p_tits_pas/models/placeholder_registration_data.dart'; // Remplacé -import '../../models/user_registration_data.dart'; // Import du vrai modèle -import '../../utils/data_generator.dart'; // Import du générateur -import '../../models/card_assets.dart'; // Import des enums de cartes +import '../../../models/user_registration_data.dart'; // Import du vrai modèle +import '../../../utils/data_generator.dart'; // Import du générateur +import '../../../models/card_assets.dart'; // Import des enums de cartes class ParentRegisterStep4Screen extends StatefulWidget { final UserRegistrationData registrationData; // Accepte les données diff --git a/frontend/lib/screens/auth/parent_register_step5_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart similarity index 98% rename from frontend/lib/screens/auth/parent_register_step5_screen.dart rename to frontend/lib/screens/auth/parent/parent_register_step5_screen.dart index 24a914c..adedd3c 100644 --- a/frontend/lib/screens/auth/parent_register_step5_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import '../../models/user_registration_data.dart'; // Utilisation du vrai modèle -import '../../widgets/image_button.dart'; // Import du ImageButton -import '../../models/card_assets.dart'; // Import des enums de cartes +import '../../../models/user_registration_data.dart'; // Utilisation du vrai modèle +import '../../../widgets/image_button.dart'; // Import du ImageButton +import '../../../models/card_assets.dart'; // Import des enums de cartes import 'package:flutter/foundation.dart' show kIsWeb; -import '../../widgets/custom_decorated_text_field.dart'; // Import du CustomDecoratedTextField +import '../../../widgets/custom_decorated_text_field.dart'; // Import du CustomDecoratedTextField // Nouvelle 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}) { From a442766096e827df865a4cd039344ff0ff9c4f36 Mon Sep 17 00:00:00 2001 From: Hanim Date: Tue, 12 Aug 2025 15:58:52 +0200 Subject: [PATCH 2/7] feat: Add Flutter Code Check workflow --- frontend/.github/workflows/flutter-check.yml | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 frontend/.github/workflows/flutter-check.yml diff --git a/frontend/.github/workflows/flutter-check.yml b/frontend/.github/workflows/flutter-check.yml new file mode 100644 index 0000000..b7b9f8f --- /dev/null +++ b/frontend/.github/workflows/flutter-check.yml @@ -0,0 +1,34 @@ +name: Flutter Code Check + +on: + push: + branches: [main, dev, feature/*, hotfix] + pull_request: + branches: [main, dev] + +jobs: + flutter-check: + name: Analyse & Test Flutter + runs-on: ubuntu-latest + + steps: + - name: ⬇️ Checkout code + uses: actions/checkout@v3 + + - name: 💡 Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.19.0' # ou celle que tu utilises + channel: stable + + - name: 📦 Install dependencies + run: flutter pub get + + - name: 🔍 Dart Analyzer + run: flutter analyze + + # - name: 🧪 Run tests (if present) + # run: flutter test || echo "No tests found" + + - name: 🧱 Build (Flutter Web) + run: flutter build web --release From 14efccc711a53485ff418f03eb250ae7b0f53633 Mon Sep 17 00:00:00 2001 From: Hanim Date: Tue, 12 Aug 2025 16:16:54 +0200 Subject: [PATCH 3/7] feat: Refactor user AM Part 1 registration data model and update registration screens --- ...art => parent_user_registration_data.dart} | 0 frontend/lib/navigation/app_router.dart | 9 +- .../auth/am/am_register_step1_sceen.dart | 251 +++++++++++++++++- .../parent/parent_register_step1_screen.dart | 2 +- .../parent/parent_register_step2_screen.dart | 2 +- .../parent/parent_register_step3_screen.dart | 2 +- .../parent/parent_register_step4_screen.dart | 2 +- .../parent/parent_register_step5_screen.dart | 2 +- .../screens/auth/register_choice_screen.dart | 2 + frontend/lib/widgets/FormFieldConfig.dart | 114 ++++++++ 10 files changed, 371 insertions(+), 15 deletions(-) rename frontend/lib/models/{user_registration_data.dart => parent_user_registration_data.dart} (100%) create mode 100644 frontend/lib/widgets/FormFieldConfig.dart diff --git a/frontend/lib/models/user_registration_data.dart b/frontend/lib/models/parent_user_registration_data.dart similarity index 100% rename from frontend/lib/models/user_registration_data.dart rename to frontend/lib/models/parent_user_registration_data.dart diff --git a/frontend/lib/navigation/app_router.dart b/frontend/lib/navigation/app_router.dart index 397113d..bb431bf 100644 --- a/frontend/lib/navigation/app_router.dart +++ b/frontend/lib/navigation/app_router.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:p_tits_pas/screens/auth/am/am_register_step1_sceen.dart'; import '../screens/auth/login_screen.dart'; import '../screens/auth/register_choice_screen.dart'; import '../screens/auth/parent/parent_register_step1_screen.dart'; @@ -8,7 +9,7 @@ import '../screens/auth/parent/parent_register_step3_screen.dart'; import '../screens/auth/parent/parent_register_step4_screen.dart'; import '../screens/auth/parent/parent_register_step5_screen.dart'; import '../screens/home/home_screen.dart'; -import '../models/user_registration_data.dart'; +import '../models/parent_user_registration_data.dart'; class AppRouter { static const String login = '/login'; @@ -18,6 +19,8 @@ class AppRouter { static const String parentRegisterStep3 = '/parent-register/step3'; static const String parentRegisterStep4 = '/parent-register/step4'; static const String parentRegisterStep5 = '/parent-register/step5'; + + static const String amRegisterStep1 = '/am-register/step1'; static const String home = '/home'; static Route generateRoute(RouteSettings settings) { @@ -74,6 +77,10 @@ class AppRouter { } slideTransition = true; break; + case amRegisterStep1: + screen = const AmRegisterStep1Screen(); + slideTransition = true; + break; case home: screen = const HomeScreen(); break; diff --git a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart index 9fb1909..0664857 100644 --- a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart @@ -1,17 +1,250 @@ - import 'package:flutter/material.dart'; +import 'package:p_tits_pas/models/card_assets.dart'; +import 'package:p_tits_pas/models/parent_user_registration_data.dart'; +import 'package:p_tits_pas/utils/data_generator.dart'; +import 'package:p_tits_pas/widgets/FormFieldConfig.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'dart:math' as math; + + +class AmRegisterStep1Screen extends StatefulWidget { + const AmRegisterStep1Screen({super.key}); + @override + State createState() => _AmRegisterStep1ScreenState(); +} + +class _AmRegisterStep1ScreenState extends State { + final _formKey = GlobalKey(); + late UserRegistrationData _registrationData; + + final _lastNameController = TextEditingController(); + final _firstNameController = TextEditingController(); + final _phoneController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + final _confirmPasswordController = TextEditingController(); + final _addressController = TextEditingController(); + final _postalCodeController = TextEditingController(); + final _cityController = TextEditingController(); -class AmRegisterStep1Screen extends StatelessWidget { - const AmRegisterStep1Screen({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Étape 1 - Inscription AM'), + void initState() { + super.initState(); + _registrationData = UserRegistrationData(); + _generateAndFillData(); + } + + void _generateAndFillData() { + final String genFirstName = DataGenerator.firstName(); + final String genLastName = DataGenerator.lastName(); + + _addressController.text = DataGenerator.address(); + _postalCodeController.text = DataGenerator.postalCode(); + _cityController.text = DataGenerator.city(); + _firstNameController.text = genFirstName; + _lastNameController.text = genLastName; + _phoneController.text = DataGenerator.phone(); + _emailController.text = DataGenerator.email(genFirstName, genLastName); + _passwordController.text = DataGenerator.password(); + _confirmPasswordController.text = _passwordController.text; + } + + @override + void dispose() { + _lastNameController.dispose(); + _firstNameController.dispose(); + _phoneController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + _confirmPasswordController.dispose(); + _addressController.dispose(); + _postalCodeController.dispose(); + _cityController.dispose(); + super.dispose(); + } + + List> get formFields => [ + [ + ModularFormField( + label: 'Nom', + hint: 'Votre nom de famille', + controller: _lastNameController, + isRequired: true, + flex: 12, ), - body: const Center( - child: Text('Contenu de l\'étape 1'), + ModularFormField( + label: 'Prénom', + hint: 'Votre prénom', + controller: _firstNameController, + isRequired: true, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Téléphone', + hint: 'Votre numéro de téléphone', + controller: _phoneController, + keyboardType: TextInputType.phone, + flex: 12, + ), + ModularFormField( + label: 'Email', + hint: 'Votre adresse email', + controller: _emailController, + keyboardType: TextInputType.emailAddress, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Mot de passe', + hint: 'Votre mot de passe', + controller: _passwordController, + isPassword: true, + validator: (value) { + if (value == null || value.isEmpty) return 'Mot de passe requis'; + if (value.length < 6) return '6 caractères minimum'; + return null; + }, + isRequired: true, + flex: 12, + ), + ModularFormField( + label: 'Confirmer le mot de passe', + hint: 'Confirmez votre mot de passe', + controller: _confirmPasswordController, + isPassword: true, + validator: (value) { + if (value == null || value.isEmpty) return 'Mot de passe requis'; + if (value != _passwordController.text) return 'Les mots de passe ne correspondent pas'; + return null; + }, + isRequired: true, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Adresse (N° et Rue)', + hint: 'Numéro et nom de votre rue', + controller: _addressController, + isRequired: true, + ), + ], + [ + ModularFormField( + label: 'Code postal', + hint: 'Votre code postal', + controller: _postalCodeController, + keyboardType: TextInputType.number, + isRequired: true, + flex: 1, + ), + ModularFormField( + label: 'Ville', + hint: 'Votre ville', + controller: _cityController, + flex: 4, + isRequired: true, + ), + ], + ]; + + void _handleSubmit() { + if (_formKey.currentState?.validate() ?? false) { + _registrationData.updateParent1( + ParentData( + firstName: _firstNameController.text, + lastName: _lastNameController.text, + address: _addressController.text, + postalCode: _postalCodeController.text, + city: _cityController.text, + phone: _phoneController.text, + email: _emailController.text, + password: _passwordController.text, + ), + ); + Navigator.pushNamed(context, '/am-register/step2', arguments: _registrationData); + } + } + + Widget build(BuildContext 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.repeat, + ), + ), + Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Étape 1/5', + style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), + ), + const SizedBox(height: 10), + Text( + 'Informations de l\'assistante maternelle', + 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: 570), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(CardColorHorizontal.lavender.path), + fit: BoxFit.fill, + ), + ), + child: ModularForm( + formKey: _formKey, + fieldGroups: formFields, + ), + ), + ], + ), + ), + ), + 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: () => Navigator.pop(context), + tooltip: 'Retour', + ), + ), + Positioned( + top: screenSize.height / 2 - 20, + right: 40, + child: IconButton( + icon: Image.asset('assets/images/chevron_right.png', height: 40), + onPressed: _handleSubmit, + tooltip: 'Suivant', + ), + ), + ], ), ); } diff --git a/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart index 6f04062..9058360 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step1_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron -import '../../../models/user_registration_data.dart'; // Import du modèle de données +import '../../../models/parent_user_registration_data.dart'; // Import du modèle de données import '../../../utils/data_generator.dart'; // Import du générateur de données import '../../../widgets/custom_app_text_field.dart'; // Import du widget CustomAppTextField import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart index f1008c0..38879fc 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step2_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:math' as math; // Pour la rotation du chevron -import '../../../models/user_registration_data.dart'; // Import du modèle +import '../../../models/parent_user_registration_data.dart'; // Import du modèle import '../../../utils/data_generator.dart'; // Import du générateur import '../../../widgets/custom_app_text_field.dart'; // Import du widget import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart index 4797050..0dbfa8d 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step3_screen.dart @@ -9,7 +9,7 @@ import 'dart:io' show File, Platform; // Ajout de Platform import 'package:flutter/foundation.dart' show kIsWeb; // Import pour kIsWeb import '../../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField import '../../../widgets/app_custom_checkbox.dart'; // Import du nouveau widget Checkbox -import '../../../models/user_registration_data.dart'; // Import du modèle de données +import '../../../models/parent_user_registration_data.dart'; // Import du modèle de données import '../../../utils/data_generator.dart'; // Import du générateur import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart index c4a009e..66d505c 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step4_screen.dart @@ -4,7 +4,7 @@ import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart'; // Import import 'dart:math' as math; // Pour la rotation du chevron import 'package:p_tits_pas/widgets/app_custom_checkbox.dart'; // Import de la checkbox personnalisée // import 'package:p_tits_pas/models/placeholder_registration_data.dart'; // Remplacé -import '../../../models/user_registration_data.dart'; // Import du vrai modèle +import '../../../models/parent_user_registration_data.dart'; // Import du vrai modèle import '../../../utils/data_generator.dart'; // Import du générateur import '../../../models/card_assets.dart'; // Import des enums de cartes diff --git a/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart index adedd3c..7f85fec 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import '../../../models/user_registration_data.dart'; // Utilisation du vrai modèle +import '../../../models/parent_user_registration_data.dart'; // Utilisation du vrai modèle import '../../../widgets/image_button.dart'; // Import du ImageButton import '../../../models/card_assets.dart'; // Import des enums de cartes import 'package:flutter/foundation.dart' show kIsWeb; diff --git a/frontend/lib/screens/auth/register_choice_screen.dart b/frontend/lib/screens/auth/register_choice_screen.dart index 0b6bd8a..457ef1f 100644 --- a/frontend/lib/screens/auth/register_choice_screen.dart +++ b/frontend/lib/screens/auth/register_choice_screen.dart @@ -100,6 +100,8 @@ class RegisterChoiceScreen extends StatelessWidget { onPressed: () { // TODO: Naviguer vers l'écran d'inscription assmat print('Choix: Assistante Maternelle'); + Navigator.pushNamed(context, '/am-register/step1'); + }, ), ], diff --git a/frontend/lib/widgets/FormFieldConfig.dart b/frontend/lib/widgets/FormFieldConfig.dart new file mode 100644 index 0000000..6e14cc7 --- /dev/null +++ b/frontend/lib/widgets/FormFieldConfig.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:p_tits_pas/widgets/custom_app_text_field.dart'; + +class ModularFormField { + final String label; + final String hint; + final TextEditingController controller; + final TextInputType? keyboardType; + final bool isPassword; + final String? Function(String?)? validator; + final bool isRequired; + final int flex; + + ModularFormField({ + required this.label, + required this.hint, + required this.controller, + this.keyboardType, + this.isPassword = false, + this.validator, + this.isRequired = false, + this.flex = 1, + }); +} + +class ModularForm extends StatelessWidget { + final List> fieldGroups; + final GlobalKey formKey; + final double? width; + final EdgeInsets padding; + final String? title; + final VoidCallback? onSubmit; + final String submitLabel; + + const ModularForm({ + super.key, + required this.fieldGroups, + required this.formKey, + this.width, + this.padding = const EdgeInsets.all(20), + this.title, + this.onSubmit, + this.submitLabel = "Suivant", + }); + + @override + Widget build(BuildContext context) { + return Container( + width: width ?? MediaQuery.of(context).size.width * 0.6, + padding: padding, + child: Form( + key: formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title != null) ...[ + Text( + title!, + style: GoogleFonts.merienda( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 20), + ], + ...fieldGroups.map((group) { + return Column( + children: [ + Row( + children: group.asMap().entries.map((entry) { + final index = entry.key; + final field = entry.value; + + return [ + Expanded( + flex: field.flex, + child: CustomAppTextField( + controller: field.controller, + labelText: field.label, + hintText: field.hint, + obscureText: field.isPassword, + keyboardType: field.keyboardType ?? TextInputType.text, + validator: field.validator, + style: CustomAppTextFieldStyle.beige, + fieldWidth: double.infinity, // CORRECTION PRINCIPALE + ), + ), + // Ajouter un espaceur entre les champs (sauf pour le dernier) + if (index < group.length - 1) + const Expanded( + flex: 1, + child: SizedBox(), // Espacement de 4% comme dans l'original + ), + ]; + }).expand((element) => element).toList(), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + if (onSubmit != null) + Center( + child: ElevatedButton( + onPressed: onSubmit, + child: Text(submitLabel), + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file From acda4244a9c38ec089b72628e351f4abede59bce Mon Sep 17 00:00:00 2001 From: Hanim Date: Thu, 14 Aug 2025 11:33:40 +0200 Subject: [PATCH 4/7] feat: Update registration data model for Childminder and adjust related screen AM logic --- .../lib/models/am_user_registration_data.dart | 96 +++++++++++++++++++ .../auth/am/am_register_step1_sceen.dart | 22 +++-- 2 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 frontend/lib/models/am_user_registration_data.dart diff --git a/frontend/lib/models/am_user_registration_data.dart b/frontend/lib/models/am_user_registration_data.dart new file mode 100644 index 0000000..7a04ff2 --- /dev/null +++ b/frontend/lib/models/am_user_registration_data.dart @@ -0,0 +1,96 @@ +import 'dart:io'; + +class ChildminderId { + String firstName; + String lastName; + String address; + String postalCode; + String city; + String phone; + String email; + String password; + File? profilePicture; + bool photoConsent; + + ChildminderId({ + this.firstName = '', + this.lastName = '', + this.address = '', + this.postalCode = '', + this.city = '', + this.phone = '', + this.email = '', + this.password = '', + this.profilePicture, + this.photoConsent = false, + }); +} + +class ChildminderProfessional { + String dateOfBirth; + String birthCity; + String birthCountry; + String socialSecurityNumber; // NIR + String agreementNumber; + int maxChildren; + + ChildminderProfessional({ + this.dateOfBirth = '', + this.birthCity = '', + this.birthCountry = '', + this.socialSecurityNumber = '', + this.agreementNumber = '', + this.maxChildren = 1, + }); +} + +class ChildminderRegistrationData { + ChildminderId identity; + ChildminderProfessional professional; + String presentationMessage; + bool cguAccepted; + bool isPhotoRequired; + + ChildminderRegistrationData({ + ChildminderId? identityData, + ChildminderProfessional? professionalData, + this.presentationMessage = '', + this.cguAccepted = false, + this.isPhotoRequired = false, + }) : identity = identityData ?? ChildminderId(), + professional = professionalData ?? ChildminderProfessional(); + + void updateIdentity(ChildminderId data) { + identity = data; + } + + void updateProfessional(ChildminderProfessional data) { + professional = data; + } + + void updatePresentation(String message) { + presentationMessage = message; + } + + void acceptCGU() { + cguAccepted = true; + } + + bool get isComplete { + return identity.firstName.isNotEmpty && + identity.lastName.isNotEmpty && + identity.address.isNotEmpty && + identity.postalCode.isNotEmpty && + identity.city.isNotEmpty && + identity.phone.isNotEmpty && + identity.email.isNotEmpty && + identity.password.isNotEmpty && + professional.dateOfBirth.isNotEmpty && + professional.birthCity.isNotEmpty && + professional.birthCountry.isNotEmpty && + professional.socialSecurityNumber.isNotEmpty && + professional.agreementNumber.isNotEmpty && + cguAccepted && + (!isPhotoRequired || (identity.profilePicture != null && identity.photoConsent)); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart index 0664857..85490b1 100644 --- a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:p_tits_pas/models/am_user_registration_data.dart'; import 'package:p_tits_pas/models/card_assets.dart'; -import 'package:p_tits_pas/models/parent_user_registration_data.dart'; import 'package:p_tits_pas/utils/data_generator.dart'; import 'package:p_tits_pas/widgets/FormFieldConfig.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -15,7 +15,7 @@ class AmRegisterStep1Screen extends StatefulWidget { class _AmRegisterStep1ScreenState extends State { final _formKey = GlobalKey(); - late UserRegistrationData _registrationData; + late ChildminderRegistrationData _registrationData; final _lastNameController = TextEditingController(); final _firstNameController = TextEditingController(); @@ -27,11 +27,15 @@ class _AmRegisterStep1ScreenState extends State { final _postalCodeController = TextEditingController(); final _cityController = TextEditingController(); + // File? _selectedImage; + // bool _photoConsent = false; + // final ImagePicker _picker = ImagePicker(); + @override void initState() { super.initState(); - _registrationData = UserRegistrationData(); + _registrationData = ChildminderRegistrationData(); _generateAndFillData(); } @@ -152,10 +156,10 @@ class _AmRegisterStep1ScreenState extends State { ], ]; - void _handleSubmit() { + void _handleSubmit() { if (_formKey.currentState?.validate() ?? false) { - _registrationData.updateParent1( - ParentData( + _registrationData.updateIdentity( + ChildminderId( firstName: _firstNameController.text, lastName: _lastNameController.text, address: _addressController.text, @@ -166,10 +170,12 @@ class _AmRegisterStep1ScreenState extends State { password: _passwordController.text, ), ); - Navigator.pushNamed(context, '/am-register/step2', arguments: _registrationData); + Navigator.pushNamed(context, '/am-register/step2', + arguments: _registrationData); } } + @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; @@ -189,7 +195,7 @@ class _AmRegisterStep1ScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Étape 1/5', + 'Étape 1/4', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), ), const SizedBox(height: 10), From 01a7937004c0c539f5bd3648f915e4437757ef42 Mon Sep 17 00:00:00 2001 From: Hanim Date: Mon, 18 Aug 2025 16:37:27 +0200 Subject: [PATCH 5/7] feat: Implement multi-step registration for Childminder with data validation and summary display - Added routes for registration steps 2, 3, and 4 in app_router.dart. - Created AmRegisterStep2Screen for entering professional details including birth date, city, country, social security number, agreement number, and max children. - Implemented validation for social security number and max children fields. - Developed AmRegisterStep3Screen for entering a motivation message and accepting terms and conditions. - Created AmRegisterStep4Screen to display a summary of the registration data for review before submission. - Introduced SummaryCard widget for displaying user information in a structured format. - Enhanced DataGenerator utility to provide realistic data for testing. --- frontend/lib/navigation/app_router.dart | 31 ++ .../auth/am/am_register_step1_sceen.dart | 39 ++- .../auth/am/am_register_step2_sceen.dart | 251 ++++++++++++++++ .../auth/am/am_register_step3_sceen.dart | 216 ++++++++++++++ .../auth/am/am_register_step4_sceen.dart | 269 ++++++++++++++++++ .../parent/parent_register_step5_screen.dart | 3 +- frontend/lib/utils/data_generator.dart | 124 +++++++- frontend/lib/widgets/Summary.dart | 60 ++++ 8 files changed, 984 insertions(+), 9 deletions(-) create mode 100644 frontend/lib/screens/auth/am/am_register_step2_sceen.dart create mode 100644 frontend/lib/screens/auth/am/am_register_step3_sceen.dart create mode 100644 frontend/lib/screens/auth/am/am_register_step4_sceen.dart create mode 100644 frontend/lib/widgets/Summary.dart diff --git a/frontend/lib/navigation/app_router.dart b/frontend/lib/navigation/app_router.dart index bb431bf..260bd10 100644 --- a/frontend/lib/navigation/app_router.dart +++ b/frontend/lib/navigation/app_router.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:p_tits_pas/models/am_user_registration_data.dart'; import 'package:p_tits_pas/screens/auth/am/am_register_step1_sceen.dart'; +import 'package:p_tits_pas/screens/auth/am/am_register_step2_sceen.dart'; +import 'package:p_tits_pas/screens/auth/am/am_register_step3_sceen.dart'; +import 'package:p_tits_pas/screens/auth/am/am_register_step4_sceen.dart'; import '../screens/auth/login_screen.dart'; import '../screens/auth/register_choice_screen.dart'; import '../screens/auth/parent/parent_register_step1_screen.dart'; @@ -21,6 +25,9 @@ class AppRouter { static const String parentRegisterStep5 = '/parent-register/step5'; static const String amRegisterStep1 = '/am-register/step1'; + static const String amRegisterStep2 = '/am-register/step2'; + static const String amRegisterStep3 = '/am-register/step3'; + static const String amRegisterStep4 = '/am-register/step4'; static const String home = '/home'; static Route generateRoute(RouteSettings settings) { @@ -81,6 +88,30 @@ class AppRouter { screen = const AmRegisterStep1Screen(); slideTransition = true; break; + case amRegisterStep2: + if (args is ChildminderRegistrationData) { + screen = AmRegisterStep2Screen(registrationData: args); + } else { + screen = AmRegisterStep2Screen(registrationData: ChildminderRegistrationData()); + } + slideTransition = true; + break; + case amRegisterStep3: + if (args is ChildminderRegistrationData) { + screen = AmRegisterStep3Screen(registrationData: args); + } else { + screen = AmRegisterStep3Screen(registrationData: ChildminderRegistrationData()); + } + slideTransition = true; + break; + case amRegisterStep4: + if (args is ChildminderRegistrationData) { + screen = AmRegisterStep4Screen(registrationData: args); + } else { + screen = AmRegisterStep4Screen(registrationData: ChildminderRegistrationData()); + } + slideTransition = true; + break; case home: screen = const HomeScreen(); break; diff --git a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart index 85490b1..363f71f 100644 --- a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart @@ -42,16 +42,37 @@ class _AmRegisterStep1ScreenState extends State { void _generateAndFillData() { final String genFirstName = DataGenerator.firstName(); final String genLastName = DataGenerator.lastName(); + final String genAddress = DataGenerator.address(); + final String genPostalCode = DataGenerator.postalCode(); + final String genCity = DataGenerator.city(); + final String genPhone = DataGenerator.phone(); + final String genEmail = DataGenerator.email(genFirstName, genLastName); + final String genPassword = DataGenerator.password(); - _addressController.text = DataGenerator.address(); - _postalCodeController.text = DataGenerator.postalCode(); - _cityController.text = DataGenerator.city(); + _addressController.text = genAddress; + _postalCodeController.text = genPostalCode; + _cityController.text = genCity; _firstNameController.text = genFirstName; _lastNameController.text = genLastName; - _phoneController.text = DataGenerator.phone(); - _emailController.text = DataGenerator.email(genFirstName, genLastName); - _passwordController.text = DataGenerator.password(); - _confirmPasswordController.text = _passwordController.text; + _phoneController.text = genPhone; + _emailController.text = genEmail; + _passwordController.text = genPassword; + _confirmPasswordController.text = genPassword; + + setState(() { + _registrationData.updateIdentity( + ChildminderId( + firstName: genFirstName, + lastName: genLastName, + address: genAddress, + postalCode: genPostalCode, + city: genCity, + phone: genPhone, + email: genEmail, + password: genPassword.trim(), + ), + ); + }); } @override @@ -170,6 +191,10 @@ class _AmRegisterStep1ScreenState extends State { password: _passwordController.text, ), ); + print('Vérification des données:'); + print('Adresse: ${_registrationData.identity.address}'); + print('Nom: ${_registrationData.identity.lastName}'); + print('Prénom: ${_registrationData.identity.firstName}'); Navigator.pushNamed(context, '/am-register/step2', arguments: _registrationData); } diff --git a/frontend/lib/screens/auth/am/am_register_step2_sceen.dart b/frontend/lib/screens/auth/am/am_register_step2_sceen.dart new file mode 100644 index 0000000..8436a69 --- /dev/null +++ b/frontend/lib/screens/auth/am/am_register_step2_sceen.dart @@ -0,0 +1,251 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:p_tits_pas/models/am_user_registration_data.dart'; +import 'package:p_tits_pas/models/card_assets.dart'; +import 'package:p_tits_pas/utils/data_generator.dart'; +import 'package:p_tits_pas/widgets/FormFieldConfig.dart'; +import 'dart:math' as math; + +class AmRegisterStep2Screen extends StatefulWidget { + final ChildminderRegistrationData registrationData; + const AmRegisterStep2Screen({super.key, required this.registrationData}); + + @override + State createState() => _AmRegisterStep2ScreenState(); +} + +class _AmRegisterStep2ScreenState extends State { + final _formKey = GlobalKey(); + late ChildminderRegistrationData _registrationData; + + final _dateOfBirthController = TextEditingController(); + final _birthCityController = TextEditingController(); + final _birthCountryController = TextEditingController(); + final _socialSecurityController = TextEditingController(); + final _agreementNumberController = TextEditingController(); + final _maxChildrenController = TextEditingController(); + + @override + void initState() { + super.initState(); + _registrationData = widget.registrationData; + _generateAndFillData(); + } + + void _generateAndFillData() { + _dateOfBirthController.text = DataGenerator.birthDate(); + _birthCityController.text = DataGenerator.city(); + _birthCountryController.text = "France"; + _socialSecurityController.text = DataGenerator.socialSecurityNumber(); + _agreementNumberController.text = DataGenerator.agreementNumber(); + _maxChildrenController.text = "3"; + } + + @override + void dispose() { + _dateOfBirthController.dispose(); + _birthCityController.dispose(); + _birthCountryController.dispose(); + _socialSecurityController.dispose(); + _agreementNumberController.dispose(); + _maxChildrenController.dispose(); + super.dispose(); + } + + String? _validateSocialSecurity(String? value) { + if (value == null || value.isEmpty) + return 'Numéro de sécurité sociale requis'; + + // Supprime les espaces pour la validation + String cleanValue = value.replaceAll(' ', ''); + + // Vérifie que c'est bien 13 ou 15 chiffres + if (cleanValue.length != 13 && cleanValue.length != 15) { + return 'Format invalide (13 ou 15 chiffres)'; + } + + if (!RegExp(r'^[0-9]+$').hasMatch(cleanValue)) { + return 'Seuls les chiffres sont autorisés'; + } + + return null; + } + + List> get formFields => [ + [ + ModularFormField( + label: 'Date de naissance', + hint: 'JJ/MM/AAAA', + controller: _dateOfBirthController, + keyboardType: TextInputType.datetime, + isRequired: true, + validator: (value) { + if (value == null || value.isEmpty) + return 'Date de naissance requise'; + // Validation basique du format de date + if (!RegExp(r'^[0-3][0-9]/[0-1][0-9]/[1-2][0-9]{3}$') + .hasMatch(value)) { + return 'Format invalide (JJ/MM/AAAA)'; + } + return null; + }, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Ville de naissance', + hint: 'Votre ville de naissance', + controller: _birthCityController, + isRequired: true, + flex: 12, + ), + ModularFormField( + label: 'Pays de naissance', + hint: 'Votre pays de naissance', + controller: _birthCountryController, + isRequired: true, + flex: 12, + ), + ], + [ + ModularFormField( + label: 'Numéro de Sécurité Sociale (NIR)', + hint: '1234567890123', + controller: _socialSecurityController, + keyboardType: TextInputType.number, + isRequired: true, + validator: _validateSocialSecurity, + ), + ], + [ + ModularFormField( + label: 'Numéro d\'agrément', + hint: 'Votre numéro d\'agrément', + controller: _agreementNumberController, + isRequired: true, + flex: 12, + ), + ModularFormField( + label: 'Nombre d\'enfants max', + hint: 'Ex: 3', + controller: _maxChildrenController, + keyboardType: TextInputType.number, + isRequired: true, + validator: (value) { + if (value == null || value.isEmpty) return 'Nombre requis'; + int? number = int.tryParse(value); + if (number == null || number < 1 || number > 6) { + return 'Entre 1 et 6 enfants'; + } + return null; + }, + flex: 6, + ), + ], + ]; + void _handleSubmit() { + print('Vérification des données2:'); + print('Adresse: ${_registrationData.identity.address}'); + print('Nom: ${_registrationData.identity.lastName}'); + print('Prénom: ${_registrationData.identity.firstName}'); + if (_formKey.currentState?.validate() ?? false) { + _registrationData.updateProfessional( + ChildminderProfessional( + dateOfBirth: _dateOfBirthController.text, + birthCity: _birthCityController.text, + birthCountry: _birthCountryController.text, + socialSecurityNumber: _socialSecurityController.text, + agreementNumber: _agreementNumberController.text, + maxChildren: int.tryParse(_maxChildrenController.text) ?? 1, + ), + ); + + Navigator.pushNamed(context, '/am-register/step3', + arguments: _registrationData); + } + } + + @override + Widget build(BuildContext 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.repeat, + ), + ), + Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Étape 2/4', + style: GoogleFonts.merienda( + fontSize: 16, color: Colors.black54), + ), + const SizedBox(height: 10), + Text( + 'Informations de l\'assistante maternelle', + 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: 570), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(CardColorHorizontal.peach.path), + fit: BoxFit.fill, + ), + ), + child: ModularForm( + formKey: _formKey, + fieldGroups: formFields, + ), + ), + ], + ), + ), + ), + 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: () => Navigator.pop(context), + tooltip: 'Retour', + ), + ), + Positioned( + top: screenSize.height / 2 - 20, + right: 40, + child: IconButton( + icon: Image.asset('assets/images/chevron_right.png', height: 40), + onPressed: _handleSubmit, + tooltip: 'Suivant', + ), + ), + ], + ), + ); + } +} diff --git a/frontend/lib/screens/auth/am/am_register_step3_sceen.dart b/frontend/lib/screens/auth/am/am_register_step3_sceen.dart new file mode 100644 index 0000000..94c032d --- /dev/null +++ b/frontend/lib/screens/auth/am/am_register_step3_sceen.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:p_tits_pas/models/am_user_registration_data.dart'; +import 'package:p_tits_pas/models/card_assets.dart'; +import 'package:p_tits_pas/widgets/app_custom_checkbox.dart'; +import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart'; +import 'dart:math' as math; + + +class AmRegisterStep3Screen extends StatefulWidget { + final ChildminderRegistrationData registrationData; + const AmRegisterStep3Screen({super.key, required this.registrationData}); + + @override + State createState() => _AmRegisterStep3ScreenState(); +} + +class _AmRegisterStep3ScreenState extends State { + + late ChildminderRegistrationData _registrationData; + final _presentationMessageController = TextEditingController(); + bool _cguAccepted = true; + + @override + void initState() { + super.initState(); + _registrationData = widget.registrationData; + _presentationMessageController.text = _registrationData.presentationMessage; + // _cguAccepted = _registrationData.cguAccepted; + } + + @override + void dispose() { + _presentationMessageController.dispose(); + super.dispose(); + } + + void _showCGUModal() { + const String loremIpsumText = ''' +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit. + +Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna. + +Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet. + +Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit. + +Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna. Etiam et felis dolor. + +Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. +'''; + + showDialog( + context: context, + barrierDismissible: false, // L'utilisateur doit utiliser le bouton + builder: (BuildContext dialogContext) { + return AlertDialog( + title: Text( + 'Conditions Générales d\'Utilisation', + style: GoogleFonts.merienda(fontWeight: FontWeight.bold), + ), + content: SizedBox( + width: MediaQuery.of(dialogContext).size.width * 0.7, // 70% de la largeur de l'écran + height: MediaQuery.of(dialogContext).size.height * 0.6, // 60% de la hauteur de l'écran + child: SingleChildScrollView( + child: Text( + loremIpsumText, + style: GoogleFonts.merienda(fontSize: 13), + textAlign: TextAlign.justify, + ), + ), + ), + actionsPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), + actionsAlignment: MainAxisAlignment.center, + actions: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(dialogContext).primaryColor, + padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15), + ), + child: Text( + 'Valider et Accepter', + style: GoogleFonts.merienda(fontSize: 15, color: Colors.white, fontWeight: FontWeight.bold), + ), + onPressed: () { + Navigator.of(dialogContext).pop(); // Ferme la modale + setState(() { + _cguAccepted = true; // Met à jour l'état + }); + }, + ), + ], + ); + }, + ); + } + + + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + final cardWidth = screenSize.width * 0.6; + final double imageAspectRatio = 2.0; + final cardHeight = cardWidth / imageAspectRatio; + 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, horizontal: 50.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Étape 3/4', + style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), + ), + const SizedBox(height: 20), + Text( + 'Message à destination du gestionnaire pour justifier votre demande ou ajouter des précisions', + style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), + textAlign: TextAlign.center, + ), + const SizedBox(height: 30), + Container( + width: cardWidth, + height: cardHeight, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(CardColorHorizontal.green.path), + fit: BoxFit.fill, + ), + ), + child: Padding( + padding: const EdgeInsets.all(40.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: CustomDecoratedTextField( + controller: _presentationMessageController, + hintText: 'Écrivez ici pour motiver votre demande...', + fieldHeight: cardHeight * 0.6, + maxLines: 10, + expandDynamically: true, + fontSize: 18.0, + ), + ), + const SizedBox(height: 20), + GestureDetector( + onTap: () { + if (!_cguAccepted) { + _showCGUModal(); + } + }, + child: AppCustomCheckbox( + label: 'J\'accepte les conditions générales d\'utilisation', + value: _cguAccepted, + onChanged: (newValue) { + if (!_cguAccepted) { + _showCGUModal(); + } else { + setState(() => _cguAccepted = false); + } + }, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + // Chevrons de navigation + 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: () => Navigator.pop(context), + tooltip: 'Retour', + ), + ), + Positioned( + top: screenSize.height / 2 - 20, + right: 40, + child: IconButton( + icon: Image.asset('assets/images/chevron_right.png', height: 40), + onPressed: _cguAccepted + ? () { + _registrationData.updatePresentation(_presentationMessageController.text); + _registrationData.acceptCGU(); + + Navigator.pushNamed( + context, + '/am-register/step4', + arguments: _registrationData + ); + } + : null, + tooltip: 'Suivant', + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/auth/am/am_register_step4_sceen.dart b/frontend/lib/screens/auth/am/am_register_step4_sceen.dart new file mode 100644 index 0000000..1c19ad4 --- /dev/null +++ b/frontend/lib/screens/auth/am/am_register_step4_sceen.dart @@ -0,0 +1,269 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:p_tits_pas/models/am_user_registration_data.dart'; +import 'package:p_tits_pas/models/card_assets.dart'; +import 'package:p_tits_pas/widgets/Summary.dart'; +import 'package:p_tits_pas/widgets/custom_decorated_text_field.dart'; +import 'package:p_tits_pas/widgets/image_button.dart'; + +Widget _buildDisplayFieldValue(BuildContext context, String label, String value, {bool multiLine = false, double fieldHeight = 50.0, double labelFontSize = 18.0}) { + const FontWeight labelFontWeight = FontWeight.w600; + + // Ne pas afficher le label si labelFontSize est 0 ou si label est vide + bool showLabel = label.isNotEmpty && labelFontSize > 0; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showLabel) + Text(label, style: GoogleFonts.merienda(fontSize: labelFontSize, fontWeight: labelFontWeight)), + if (showLabel) + const SizedBox(height: 4), + // Utiliser Expanded si multiLine et pas de hauteur fixe, sinon Container + multiLine && fieldHeight == null + ? Expanded( + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), + decoration: BoxDecoration( + image: const DecorationImage( + image: AssetImage('assets/images/input_field_bg.png'), + fit: BoxFit.fill, + ), + ), + child: SingleChildScrollView( // Pour le défilement si le texte dépasse + child: Text( + value.isNotEmpty ? value : '-', + style: GoogleFonts.merienda(fontSize: labelFontSize > 0 ? labelFontSize : 18.0), // Garder une taille de texte par défaut si label caché + maxLines: null, // Permettre un nombre illimité de lignes + ), + ), + ), + ) + : Container( + width: double.infinity, + height: multiLine ? null : fieldHeight, + constraints: multiLine ? BoxConstraints(minHeight: fieldHeight) : null, + padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 12.0), + decoration: BoxDecoration( + image: const DecorationImage( + image: AssetImage('assets/images/input_field_bg.png'), + fit: BoxFit.fill, + ), + ), + child: Text( + value.isNotEmpty ? value : '-', + style: GoogleFonts.merienda(fontSize: labelFontSize > 0 ? labelFontSize : 18.0), + maxLines: multiLine ? null : 1, + overflow: multiLine ? TextOverflow.visible : TextOverflow.ellipsis, + ), + ), + ], + ); +} + +class AmRegisterStep4Screen extends StatelessWidget { + final ChildminderRegistrationData registrationData; + + const AmRegisterStep4Screen({super.key, required this.registrationData}); + + Widget _buildAm1Card(BuildContext context, ChildminderRegistrationData data) { + const double verticalSpacing = 28.0; // Espacement vertical augmenté + const double labelFontSize = 22.0; // Taille de label augmentée + + List details = [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Nom:", data.identity.lastName, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Prénom:", data.identity.firstName, labelFontSize: labelFontSize)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Téléphone:", data.identity.phone, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Email:", data.identity.email, multiLine: true, labelFontSize: labelFontSize)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Adresse:", "${data.identity.address}\n${data.identity.postalCode} ${data.identity.city}".trim(), labelFontSize: labelFontSize)), + const SizedBox(width: 20), + ], + ), + ]; + return SummaryCard( + backgroundImagePath: CardColorHorizontal.peach.path, + title: 'Parent Principal', + content: details, + onEdit: () => Navigator.of(context).pushNamed('/am-register/step1', arguments: registrationData), + ); + } + + Widget _buildAm2Card(BuildContext context, ChildminderRegistrationData data) { + const double verticalSpacing = 28.0; + const double labelFontSize = 22.0; + + List myDetails = [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Date de naissance:", data.professional.dateOfBirth, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Ville de naissance:", data.professional.birthCity, labelFontSize: labelFontSize)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Pays de naissance:", data.professional.birthCountry, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Numéro de sécurité sociale:", data.professional.socialSecurityNumber, labelFontSize: labelFontSize)), + ], + ), + const SizedBox(height: verticalSpacing), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildDisplayFieldValue(context, "Numéro d'agrément:", data.professional.agreementNumber, labelFontSize: labelFontSize)), + const SizedBox(width: 20), + Expanded(child: _buildDisplayFieldValue(context, "Nombre d'enfants maximum:", data.professional.maxChildren.toString(), labelFontSize: labelFontSize)), + ], + ), + ]; + return SummaryCard( + backgroundImagePath: CardColorHorizontal.lavender.path, + title: 'Informations professionnelles', + content: myDetails, + onEdit: () => Navigator.of(context) + .pushNamed('/am-register/step2', arguments: registrationData), + ); + } + + Widget _buildMotivationCard(BuildContext context, ChildminderRegistrationData data) { + return SummaryCard( + backgroundImagePath: CardColorHorizontal.green.path, + title: 'Motivation', + content: [ + Expanded(child: CustomDecoratedTextField( + controller: TextEditingController(text: data.presentationMessage), + hintText: 'Parlez-nous de votre motivation', + fieldHeight: 200, + maxLines: 10, + expandDynamically: true, + readOnly: true, + fontSize: 18.0,)), + ], + onEdit: () => Navigator.of(context) + .pushNamed('/am-register/step3', arguments: registrationData), + ); + } + + @override + Widget build(BuildContext 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.all(40.0), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: screenSize.width / 4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Etape 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), + _buildAm1Card(context, registrationData), + const SizedBox(height: 20), + if (registrationData.professional != null) ...[ + _buildAm2Card(context, registrationData), + const SizedBox(height: 20), + ], + _buildMotivationCard(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: () { + // Vérification des données requises + _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: () => Navigator.pop(context), + 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(); + Navigator.of(context) + .pushNamedAndRemoveUntil('/login', (route) => false); + }, + ), + ], + ); + }, + ); + } +} diff --git a/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart index 7f85fec..f6121af 100644 --- a/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart +++ b/frontend/lib/screens/auth/parent/parent_register_step5_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:p_tits_pas/widgets/Summary.dart'; import '../../../models/parent_user_registration_data.dart'; // Utilisation du vrai modèle import '../../../widgets/image_button.dart'; // Import du ImageButton import '../../../models/card_assets.dart'; // Import des enums de cartes @@ -127,7 +128,7 @@ class ParentRegisterStep5Screen extends StatelessWidget { const SizedBox(height: verticalSpacing), _buildDisplayFieldValue(context, "Adresse:", "${data.address}\n${data.postalCode} ${data.city}".trim(), multiLine: true, fieldHeight: 80, labelFontSize: labelFontSize), ]; - return _SummaryCard( + return SummaryCard( backgroundImagePath: CardColorHorizontal.blue.path, title: 'Deuxième Parent', content: details, diff --git a/frontend/lib/utils/data_generator.dart b/frontend/lib/utils/data_generator.dart index a6f9077..cd8a7d8 100644 --- a/frontend/lib/utils/data_generator.dart +++ b/frontend/lib/utils/data_generator.dart @@ -26,6 +26,25 @@ class DataGenerator { 'Nous avons hâte de vous rencontrer.', 'La pédagogie Montessori nous intéresse.' ]; + static final List _frenchCities = [ + 'Paris', 'Lyon', 'Marseille', 'Toulouse', 'Nice', 'Nantes', 'Montpellier', 'Strasbourg', + 'Bordeaux', 'Lille', 'Rennes', 'Reims', 'Saint-Étienne', 'Toulon', 'Le Havre', 'Grenoble', + 'Dijon', 'Angers', 'Nîmes', 'Villeurbanne', 'Clermont-Ferrand', 'Le Mans', 'Aix-en-Provence', + 'Brest', 'Tours', 'Amiens', 'Limoges', 'Annecy', 'Boulogne-Billancourt', 'Perpignan' + ]; + + static final List _countries = [ + 'France', 'Belgique', 'Suisse', 'Canada', 'Maroc', 'Algérie', 'Tunisie', 'Sénégal', + 'Côte d\'Ivoire', 'Madagascar', 'Espagne', 'Italie', 'Portugal', 'Allemagne', 'Royaume-Uni' + ]; + + static final List _childminderPresentations = [ + 'Bonjour,\n\nJe suis assistante maternelle agréée depuis plusieurs années et je souhaite rejoindre votre plateforme. J\'ai de l\'expérience avec les enfants de tous âges et je privilégie un accompagnement bienveillant.\n\nCordialement', + 'Madame, Monsieur,\n\nTitulaire d\'un agrément d\'assistante maternelle, je propose un accueil personnalisé dans un environnement sécurisé et stimulant. Je suis disponible pour discuter de vos besoins.\n\nBien à vous', + 'Bonjour,\n\nAssistante maternelle passionnée, je propose un accueil de qualité dans ma maison adaptée aux enfants. J\'ai suivi plusieurs formations et je suis disponible à temps plein.\n\nÀ bientôt', + 'Cher gestionnaire,\n\nJe suis une professionnelle expérimentée dans la garde d\'enfants. Mon domicile est aménagé pour accueillir les petits dans les meilleures conditions. Je serais ravie de faire partie de votre réseau.\n\nCordialement', + 'Bonjour,\n\nDepuis 5 ans, j\'exerce comme assistante maternelle avec passion. Je propose des activités d\'éveil adaptées et un suivi personnalisé de chaque enfant. Mon agrément me permet d\'accueillir jusqu\'à 4 enfants.\n\nBien cordialement' + ]; static String firstName() => _firstNames[_random.nextInt(_firstNames.length)]; static String lastName() => _lastNames[_random.nextInt(_lastNames.length)]; @@ -62,4 +81,107 @@ class DataGenerator { } return chosenSnippets.join(' '); } -} \ No newline at end of file + static String birthDate() { + final now = DateTime.now(); + final age = _random.nextInt(31) + 25; // Entre 25 et 55 ans + final birthYear = now.year - age; + final birthMonth = _random.nextInt(12) + 1; + final birthDay = _random.nextInt(28) + 1; + return "${birthDay.toString().padLeft(2, '0')}/${birthMonth.toString().padLeft(2, '0')}/${birthYear}"; + } + + /// Génère une ville de naissance française + static String birthCity() => _frenchCities[_random.nextInt(_frenchCities.length)]; + + /// Génère un pays de naissance + static String birthCountry() => _countries[_random.nextInt(_countries.length)]; + + /// Génère un numéro de sécurité sociale français (NIR) + static String socialSecurityNumber() { + // Format NIR français : 1 YYMM DD CCC KK + // 1 = sexe (1 homme, 2 femme) + final sex = _random.nextBool() ? '1' : '2'; + + // YY = année de naissance (2 derniers chiffres) + final currentYear = DateTime.now().year; + final birthYear = currentYear - (_random.nextInt(31) + 25); // 25-55 ans + final yy = (birthYear % 100).toString().padLeft(2, '0'); + + // MM = mois de naissance + final mm = (_random.nextInt(12) + 1).toString().padLeft(2, '0'); + + // DD = département de naissance (01-95) + final dd = (_random.nextInt(95) + 1).toString().padLeft(2, '0'); + + // CCC = numéro d'ordre (001-999) + final ccc = (_random.nextInt(999) + 1).toString().padLeft(3, '0'); + + // KK = clé de contrôle (simulation) + final kk = _random.nextInt(100).toString().padLeft(2, '0'); + + return '$sex$yy$mm$dd$ccc$kk'; + } + + /// Génère un numéro d'agrément pour assistante maternelle + static String agreementNumber() { + final year = DateTime.now().year; + final dept = _random.nextInt(95) + 1; // Département 01-95 + final sequence = _random.nextInt(9999) + 1; // Numéro de séquence + return 'AM${dept.toString().padLeft(2, '0')}$year${sequence.toString().padLeft(4, '0')}'; + } + + /// Génère un nombre d'enfants maximum pour l'agrément (1-4) + static int maxChildren() => _random.nextInt(4) + 1; + + /// Génère un message de présentation pour assistante maternelle + static String childminderPresentation() => + _childminderPresentations[_random.nextInt(_childminderPresentations.length)]; + + /// Génère un âge d'enfant en mois (0-36 mois) + static int childAgeInMonths() => _random.nextInt(37); + + /// Génère une durée d'expérience en années (1-15 ans) + static int experienceYears() => _random.nextInt(15) + 1; + + /// Génère un tarif horaire (entre 3.50€ et 6.00€) + static double hourlyRate() { + final rate = 3.50 + (_random.nextDouble() * 2.50); + return double.parse(rate.toStringAsFixed(2)); + } + + /// Génère des disponibilités (jours de la semaine) + static List availability() { + final days = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']; + final availableDays = []; + + // Au moins 3 jours de disponibilité + final minDays = 3; + final maxDays = days.length; + final numDays = _random.nextInt(maxDays - minDays + 1) + minDays; + + final selectedIndices = []; + while (selectedIndices.length < numDays) { + final index = _random.nextInt(days.length); + if (!selectedIndices.contains(index)) { + selectedIndices.add(index); + availableDays.add(days[index]); + } + } + + return availableDays; + } + + /// Génère des horaires (format "08h30-17h30") + static String workingHours() { + final startHours = [7, 8, 9]; + final endHours = [16, 17, 18, 19]; + + final startHour = startHours[_random.nextInt(startHours.length)]; + final endHour = endHours[_random.nextInt(endHours.length)]; + + final startMinutes = _random.nextBool() ? '00' : '30'; + final endMinutes = _random.nextBool() ? '00' : '30'; + + return '${startHour}h$startMinutes-${endHour}h$endMinutes'; + } +} \ No newline at end of file diff --git a/frontend/lib/widgets/Summary.dart b/frontend/lib/widgets/Summary.dart new file mode 100644 index 0000000..7f486b7 --- /dev/null +++ b/frontend/lib/widgets/Summary.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +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: Column( + children: [ + Row( + children: [ + Expanded( + child: Text( + title, + style: GoogleFonts.merienda(fontSize: 28, fontWeight: FontWeight.w600), + textAlign: TextAlign.center, + ), + ), + IconButton( + icon: const Icon(Icons.edit, color: Colors.black54, size: 28), + onPressed: onEdit, + tooltip: 'Modifier', + ), + ], + ), + const SizedBox(height: 18), + Expanded( + child: Column( + children: content, + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file From 332f4be7d7879f8f9da8e9ecb886b4801e48fd59 Mon Sep 17 00:00:00 2001 From: Hanim Date: Mon, 18 Aug 2025 16:48:04 +0200 Subject: [PATCH 6/7] feat: Update identity information labels in registration screens for clarity --- frontend/lib/screens/auth/am/am_register_step1_sceen.dart | 2 +- frontend/lib/screens/auth/am/am_register_step2_sceen.dart | 2 +- frontend/lib/screens/auth/am/am_register_step4_sceen.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart index 363f71f..1f22d70 100644 --- a/frontend/lib/screens/auth/am/am_register_step1_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step1_sceen.dart @@ -225,7 +225,7 @@ class _AmRegisterStep1ScreenState extends State { ), const SizedBox(height: 10), Text( - 'Informations de l\'assistante maternelle', + 'Informations d\'identité de l\'assistante maternelle', style: GoogleFonts.merienda( fontSize: 24, fontWeight: FontWeight.bold, diff --git a/frontend/lib/screens/auth/am/am_register_step2_sceen.dart b/frontend/lib/screens/auth/am/am_register_step2_sceen.dart index 8436a69..f6a86be 100644 --- a/frontend/lib/screens/auth/am/am_register_step2_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step2_sceen.dart @@ -192,7 +192,7 @@ class _AmRegisterStep2ScreenState extends State { ), const SizedBox(height: 10), Text( - 'Informations de l\'assistante maternelle', + 'Informations professionnelles de l\'assistante maternelle', style: GoogleFonts.merienda( fontSize: 24, fontWeight: FontWeight.bold, diff --git a/frontend/lib/screens/auth/am/am_register_step4_sceen.dart b/frontend/lib/screens/auth/am/am_register_step4_sceen.dart index 1c19ad4..f54a2bc 100644 --- a/frontend/lib/screens/auth/am/am_register_step4_sceen.dart +++ b/frontend/lib/screens/auth/am/am_register_step4_sceen.dart @@ -100,7 +100,7 @@ class AmRegisterStep4Screen extends StatelessWidget { ]; return SummaryCard( backgroundImagePath: CardColorHorizontal.peach.path, - title: 'Parent Principal', + title: 'Informations d’identité', content: details, onEdit: () => Navigator.of(context).pushNamed('/am-register/step1', arguments: registrationData), ); From 7d97de308643001c6111c95b2b16d580a6db4fb8 Mon Sep 17 00:00:00 2001 From: Hanim Date: Mon, 25 Aug 2025 11:27:07 +0200 Subject: [PATCH 7/7] feat: Add legal and privacy routes to AppRouter --- frontend/lib/navigation/app_router.dart | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/lib/navigation/app_router.dart b/frontend/lib/navigation/app_router.dart index 260bd10..3fd8b6c 100644 --- a/frontend/lib/navigation/app_router.dart +++ b/frontend/lib/navigation/app_router.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:p_tits_pas/models/am_user_registration_data.dart'; import 'package:p_tits_pas/screens/auth/am/am_register_step1_sceen.dart'; import 'package:p_tits_pas/screens/auth/am/am_register_step2_sceen.dart'; import 'package:p_tits_pas/screens/auth/am/am_register_step3_sceen.dart'; import 'package:p_tits_pas/screens/auth/am/am_register_step4_sceen.dart'; +import 'package:p_tits_pas/screens/legal/legal_page.dart'; +import 'package:p_tits_pas/screens/legal/privacy_page.dart'; import '../screens/auth/login_screen.dart'; import '../screens/auth/register_choice_screen.dart'; import '../screens/auth/parent/parent_register_step1_screen.dart'; @@ -18,6 +19,8 @@ import '../models/parent_user_registration_data.dart'; class AppRouter { static const String login = '/login'; static const String registerChoice = '/register-choice'; + static const String legal = '/legal'; + static const String privacy = '/privacy'; static const String parentRegisterStep1 = '/parent-register/step1'; static const String parentRegisterStep2 = '/parent-register/step2'; static const String parentRegisterStep3 = '/parent-register/step3'; @@ -48,8 +51,12 @@ class AppRouter { screen = const RegisterChoiceScreen(); slideTransition = true; break; - case parentRegisterStep1: - screen = const ParentRegisterStep1Screen(); + case legal: + screen = const LegalPage(); + slideTransition = true; + break; + case privacy: + screen = const PrivacyPage(); slideTransition = true; break; case parentRegisterStep2: