Compare commits

..

2 Commits

Author SHA1 Message Date
2fb53d20cf feat(auth): API inscription parent complete - Workflow 6 etapes
- Refonte complete de l'inscription parent (Tickets #18 et #19 fusionnes)
- Workflow CDC 6 etapes en 1 transaction atomique :
  * Etape 1 : Informations Parent 1 (obligatoire)
  * Etape 2 : Informations Parent 2 / Co-parent (optionnel)
  * Etape 3 : Enfants avec photos (au moins 1 requis)
  * Etape 4 : Presentation du dossier (optionnel)
  * Etape 5 : Acceptation CGU + Privacy (obligatoire)
  * Etape 6 : Recapitulatif -> VALIDATION

Modifications techniques :
- Nouveau DTO RegisterParentCompletDto (Parent1+Parent2+Enfants+Presentation+CGU)
- Nouveau DTO EnfantInscriptionDto pour les enfants
- Methode inscrireParentComplet() : transaction unique
- Generation tokens creation MDP (Parent 1 + Parent 2)
- Gestion photos enfants (base64 -> fichier)
- Liens parents-enfants via table parents_children
- Statut en_attente pour validation gestionnaire

Tests :
- Teste avec couple MARTIN + 3 triples (Emma, Noah, Lea)
- 2 parents crees + 3 enfants lies

Documentation :
- Ajout 99_REGLES-CODAGE.md : Convention francais/anglais
- Tickets Gitea mis a jour (#18 refonte, #19 ferme)

Refs: #18, #19
2025-12-01 22:42:56 +01:00
dfad408902 [Ticket #18] API Inscription Parent - Étape 3 (Enfants)
 Modifications apportées :
- Rendu le champ 'genre' obligatoire dans CreateEnfantsDto (conforme CDC)
- Ajout upload photo avec Multer (max 5MB, formats jpg/jpeg/png/gif)
- Rattachement automatique au co-parent s'il existe
- Création dossier /app/uploads/photos dans Dockerfile avec permissions
- Gestion enfants à naître vs nés
- Gestion consentement photo avec horodatage

 Tests réalisés :
- Création enfant 'actif' avec date de naissance (Emma Martin)
- Création enfant avec rattachement aux 2 parents (Noah Martin)
- Création enfant 'a_naitre' avec date prévue (Léa Martin)
- Vérification base de données : enfants bien rattachés aux parents

Refs: #18
2025-12-01 16:28:45 +01:00
6 changed files with 169 additions and 133 deletions

View File

@ -200,10 +200,16 @@ export class AuthService {
adresse: dto.adresse, adresse: dto.adresse,
code_postal: dto.code_postal, code_postal: dto.code_postal,
ville: dto.ville, ville: dto.ville,
profession: dto.profession,
situation_familiale: dto.situation_familiale,
token_creation_mdp: tokenCreationMdp, token_creation_mdp: tokenCreationMdp,
token_creation_mdp_expire_le: tokenExpiration, token_creation_mdp_expire_le: tokenExpiration,
}); });
if (dto.date_naissance) {
parent1.date_naissance = new Date(dto.date_naissance);
}
const savedParent1 = await manager.save(Users, parent1); const savedParent1 = await manager.save(Users, parent1);
// Créer Parent 2 si renseigné // Créer Parent 2 si renseigné
@ -321,10 +327,16 @@ export class AuthService {
adresse: dto.adresse, adresse: dto.adresse,
code_postal: dto.code_postal, code_postal: dto.code_postal,
ville: dto.ville, ville: dto.ville,
profession: dto.profession,
situation_familiale: dto.situation_familiale,
token_creation_mdp: tokenCreationMdp, token_creation_mdp: tokenCreationMdp,
token_creation_mdp_expire_le: dateExpiration, token_creation_mdp_expire_le: dateExpiration,
}); });
if (dto.date_naissance) {
parent1.date_naissance = new Date(dto.date_naissance);
}
const parent1Enregistre = await manager.save(Users, parent1); const parent1Enregistre = await manager.save(Users, parent1);
let parent2Enregistre: Users | null = null; let parent2Enregistre: Users | null = null;

View File

@ -66,6 +66,22 @@ export class RegisterParentCompletDto {
@MaxLength(150) @MaxLength(150)
ville?: string; ville?: string;
@ApiProperty({ example: 'Infirmière', required: false })
@IsOptional()
@IsString()
@MaxLength(150)
profession?: string;
@ApiProperty({ enum: SituationFamilialeType, example: SituationFamilialeType.MARIE, required: false })
@IsOptional()
@IsEnum(SituationFamilialeType)
situation_familiale?: SituationFamilialeType;
@ApiProperty({ example: '1990-04-03', required: false })
@IsOptional()
@IsDateString()
date_naissance?: string;
// ============================================ // ============================================
// ÉTAPE 2 : PARENT 2 / CO-PARENT (Optionnel) // ÉTAPE 2 : PARENT 2 / CO-PARENT (Optionnel)
// ============================================ // ============================================

View File

@ -59,6 +59,22 @@ export class RegisterParentDto {
@MaxLength(150) @MaxLength(150)
ville?: string; ville?: string;
@ApiProperty({ example: 'Infirmière', required: false })
@IsOptional()
@IsString()
@MaxLength(150)
profession?: string;
@ApiProperty({ enum: SituationFamilialeType, example: SituationFamilialeType.MARIE, required: false })
@IsOptional()
@IsEnum(SituationFamilialeType)
situation_familiale?: SituationFamilialeType;
@ApiProperty({ example: '1990-04-03', required: false })
@IsOptional()
@IsDateString()
date_naissance?: string;
// === Informations co-parent (optionnel) === // === Informations co-parent (optionnel) ===
@ApiProperty({ example: 'thomas.martin@ptits-pas.fr', required: false }) @ApiProperty({ example: 'thomas.martin@ptits-pas.fr', required: false })
@IsOptional() @IsOptional()

View File

@ -58,34 +58,32 @@ Ajouter un champ pour stocker la présentation du dossier parent (étape 4 de l'
--- ---
### Ticket #3 : [BDD] Ajout gestion tokens création mot de passe ### Ticket #3 : [BDD] Ajout gestion tokens création mot de passe
**Estimation** : 30min **Estimation** : 30min
**Labels** : `bdd`, `p0-bloquant`, `security` **Labels** : `bdd`, `p0-bloquant`, `security`
**Statut** : ✅ TERMINÉ (Fermé le 2025-11-28)
**Description** : **Description** :
Ajouter les champs nécessaires pour gérer les tokens de création de mot de passe (workflow sans MDP lors inscription). Ajouter les champs nécessaires pour gérer les tokens de création de mot de passe (workflow sans MDP lors inscription).
**Tâches** : **Tâches** :
- [x] Ajouter `password_reset_token` UUID dans `utilisateurs` - [ ] Ajouter `password_reset_token` UUID dans `utilisateurs`
- [x] Ajouter `password_reset_expires` TIMESTAMPTZ dans `utilisateurs` - [ ] Ajouter `password_reset_expires` TIMESTAMPTZ dans `utilisateurs`
- [x] Créer migration Prisma - [ ] Créer migration Prisma
- [x] Tester migration - [ ] Tester migration
--- ---
### Ticket #4 : [BDD] Ajout champ genre obligatoire enfants ### Ticket #4 : [BDD] Ajout champ genre obligatoire enfants
**Estimation** : 30min **Estimation** : 30min
**Labels** : `bdd`, `p0-bloquant`, `cdc` **Labels** : `bdd`, `p0-bloquant`, `cdc`
**Statut** : ✅ TERMINÉ (Fermé le 2025-11-28)
**Description** : **Description** :
Ajouter le champ `genre` obligatoire (H/F) dans la table `enfants`. Ajouter le champ `genre` obligatoire (H/F) dans la table `enfants`.
**Tâches** : **Tâches** :
- [x] Ajouter `genre` ENUM('H', 'F') NOT NULL dans `enfants` - [ ] Ajouter `genre` ENUM('H', 'F') NOT NULL dans `enfants`
- [x] Créer migration Prisma - [ ] Créer migration Prisma
- [x] Tester migration - [ ] Tester migration
--- ---
@ -124,10 +122,9 @@ Créer la table `configuration` pour stocker les paramètres système (SMTP, app
--- ---
### Ticket #7 : [BDD] Tables documents légaux & acceptations ### Ticket #7 : [BDD] Tables documents légaux & acceptations
**Estimation** : 2h **Estimation** : 2h
**Labels** : `bdd`, `p0-bloquant`, `rgpd`, `juridique` **Labels** : `bdd`, `p0-bloquant`, `rgpd`, `juridique`
**Statut** : ✅ TERMINÉ (Fermé le 2025-11-30 - Ticket #68 sur Gitea)
**Description** : **Description** :
Créer les tables pour gérer les versions des documents légaux (CGU/Privacy) et tracer les acceptations utilisateurs. Créer les tables pour gérer les versions des documents légaux (CGU/Privacy) et tracer les acceptations utilisateurs.
@ -337,13 +334,12 @@ Ajouter la gestion du co-parent (Parent 2) dans l'endpoint d'inscription.
--- ---
### Ticket #18 : [Backend] API Inscription Parent - REFONTE (Workflow complet 6 étapes) ✅ ### Ticket #18 : [Backend] API Inscription Parent (étape 3 - Enfants)
**Estimation** : 4h **Estimation** : 4h
**Labels** : `backend`, `p2`, `auth`, `cdc`, `upload` **Labels** : `backend`, `p2`, `auth`, `cdc`, `upload`
**Statut** : ✅ TERMINÉ (Fermé le 2025-12-01)
**Description** : **Description** :
Refonte complète de l'API d'inscription parent pour gérer le workflow complet en 6 étapes dans une seule transaction. Créer l'endpoint pour ajouter des enfants lors de l'inscription parent.
**Tâches** : **Tâches** :
- [ ] Endpoint `POST /api/v1/enfants` - [ ] Endpoint `POST /api/v1/enfants`
@ -356,13 +352,12 @@ Refonte complète de l'API d'inscription parent pour gérer le workflow complet
--- ---
### Ticket #19 : [Backend] API Inscription Parent (étape 2 - Parent 2) ✅ ### Ticket #19 : [Backend] API Inscription Parent (étape 4-6 - Finalisation)
**Estimation** : 2h **Estimation** : 2h
**Labels** : `backend`, `p2`, `auth`, `cdc` **Labels** : `backend`, `p2`, `auth`, `cdc`
**Statut** : ✅ TERMINÉ (Fermé le 2025-12-01)
**Description** : **Description** :
Gestion du co-parent (Parent 2) dans l'endpoint d'inscription (intégré dans la refonte #18). Finaliser l'inscription parent (présentation, CGU, récapitulatif).
**Tâches** : **Tâches** :
- [ ] Enregistrement présentation dossier - [ ] Enregistrement présentation dossier
@ -372,13 +367,12 @@ Gestion du co-parent (Parent 2) dans l'endpoint d'inscription (intégré dans la
--- ---
### Ticket #20 : [Backend] API Inscription Parent (étape 3 - Enfants) ✅ ### Ticket #20 : [Backend] API Inscription AM (panneau 1 - Identité)
**Estimation** : 4h **Estimation** : 4h
**Labels** : `backend`, `p2`, `auth`, `cdc`, `upload` **Labels** : `backend`, `p2`, `auth`, `cdc`, `upload`
**Statut** : ✅ TERMINÉ (Fermé le 2025-12-01)
**Description** : **Description** :
Gestion des enfants dans l'endpoint d'inscription (intégré dans la refonte #18). Créer l'endpoint d'inscription Assistante Maternelle (panneau 1/5 : identité).
**Tâches** : **Tâches** :
- [ ] Endpoint `POST /api/v1/auth/register/am` - [ ] Endpoint `POST /api/v1/auth/register/am`
@ -392,13 +386,12 @@ Gestion des enfants dans l'endpoint d'inscription (intégré dans la refonte #18
--- ---
### Ticket #21 : [Backend] API Inscription Parent (étape 4-6 - Finalisation) ✅ ### Ticket #21 : [Backend] API Inscription AM (panneau 2 - Infos pro)
**Estimation** : 3h **Estimation** : 3h
**Labels** : `backend`, `p2`, `auth`, `cdc` **Labels** : `backend`, `p2`, `auth`, `cdc`
**Statut** : ✅ TERMINÉ (Fermé le 2025-12-01)
**Description** : **Description** :
Finalisation de l'inscription parent (présentation, CGU, récapitulatif - intégré dans la refonte #18). Ajouter les informations professionnelles de l'AM (panneau 2/5).
**Tâches** : **Tâches** :
- [ ] Validation NIR (15 chiffres obligatoire) - [ ] Validation NIR (15 chiffres obligatoire)
@ -624,33 +617,22 @@ Créer l'écran de création de gestionnaire (super admin uniquement).
--- ---
### Ticket #34 : [Réservé - Non utilisé] ### Ticket #34 : [Frontend] Inscription Parent - Étape 1 (Parent 1)
---
### Ticket #35 : [Réservé - Non utilisé]
---
### Ticket #36 : [Frontend] Inscription Parent - Étape 1 (Parent 1) ✅
**Estimation** : 3h **Estimation** : 3h
**Labels** : `frontend`, `p3`, `auth`, `cdc` **Labels** : `frontend`, `p3`, `auth`, `cdc`
**Statut** : ✅ TERMINÉ (PR #73 mergée le 2025-12-01)
**Description** : **Description** :
Créer le formulaire d'inscription parent - étape 1/6 (informations Parent 1). Créer le formulaire d'inscription parent - étape 1/6 (informations Parent 1).
**Tâches** : **Tâches** :
- [x] Formulaire identité Parent 1 - [ ] Formulaire identité Parent 1
- [x] Validation côté client - [ ] Validation côté client
- [x] Pas de champ mot de passe - [ ] Pas de champ mot de passe
- [x] Navigation vers étape 2 - [ ] Navigation vers étape 2
- [x] Améliorations visuelles (labels 22px, champs 20px, espacement 32px)
- [x] Correction indicateur étape 1/6
--- ---
### Ticket #37 : [Frontend] Inscription Parent - Étape 2 (Parent 2) ### Ticket #35 : [Frontend] Inscription Parent - Étape 2 (Parent 2)
**Estimation** : 3h **Estimation** : 3h
**Labels** : `frontend`, `p3`, `auth`, `cdc` **Labels** : `frontend`, `p3`, `auth`, `cdc`
@ -662,13 +644,10 @@ Créer le formulaire d'inscription parent - étape 2/6 (informations Parent 2 op
- [ ] Formulaire identité Parent 2 (conditionnel) - [ ] Formulaire identité Parent 2 (conditionnel)
- [ ] Checkbox "Même adresse" - [ ] Checkbox "Même adresse"
- [ ] Navigation vers étape 3 - [ ] Navigation vers étape 3
- [ ] Pas de champ mot de passe
- [ ] Améliorations visuelles (mêmes que Step1)
- [ ] Correction indicateur étape 2/6
--- ---
### Ticket #38 : [Frontend] Inscription Parent - Étape 3 (Enfants) ### Ticket #36 : [Frontend] Inscription Parent - Étape 3 (Enfants)
**Estimation** : 4h **Estimation** : 4h
**Labels** : `frontend`, `p3`, `auth`, `cdc`, `upload` **Labels** : `frontend`, `p3`, `auth`, `cdc`, `upload`
@ -685,7 +664,7 @@ Créer le formulaire d'inscription parent - étape 3/6 (informations enfants).
--- ---
### Ticket #39 : [Frontend] Inscription Parent - Étapes 4-6 (Finalisation) ### Ticket #37 : [Frontend] Inscription Parent - Étapes 4-6 (Finalisation)
**Estimation** : 4h **Estimation** : 4h
**Labels** : `frontend`, `p3`, `auth`, `cdc` **Labels** : `frontend`, `p3`, `auth`, `cdc`
@ -702,7 +681,7 @@ Créer les étapes finales de l'inscription parent (présentation, CGU, récapit
--- ---
### Ticket #40 : [Frontend] Inscription AM - Panneau 1 (Identité) ### Ticket #38 : [Frontend] Inscription AM - Panneau 1 (Identité)
**Estimation** : 3h **Estimation** : 3h
**Labels** : `frontend`, `p3`, `auth`, `cdc`, `upload` **Labels** : `frontend`, `p3`, `auth`, `cdc`, `upload`
@ -718,7 +697,7 @@ Créer le formulaire d'inscription AM - panneau 1/5 (identité).
--- ---
### Ticket #41 : [Frontend] Inscription AM - Panneau 2 (Infos pro) ### Ticket #39 : [Frontend] Inscription AM - Panneau 2 (Infos pro)
**Estimation** : 3h **Estimation** : 3h
**Labels** : `frontend`, `p3`, `auth`, `cdc` **Labels** : `frontend`, `p3`, `auth`, `cdc`
@ -734,7 +713,7 @@ Créer le formulaire d'inscription AM - panneau 2/5 (informations professionnell
--- ---
### Ticket #42 : [Frontend] Inscription AM - Finalisation ### Ticket #40 : [Frontend] Inscription AM - Finalisation
**Estimation** : 3h **Estimation** : 3h
**Labels** : `frontend`, `p3`, `auth`, `cdc` **Labels** : `frontend`, `p3`, `auth`, `cdc`
@ -750,7 +729,7 @@ Créer les étapes finales de l'inscription AM (présentation, CGU, récapitulat
--- ---
### Ticket #43 : [Frontend] Écran Création Mot de Passe ### Ticket #41 : [Frontend] Écran Création Mot de Passe
**Estimation** : 3h **Estimation** : 3h
**Labels** : `frontend`, `p3`, `auth` **Labels** : `frontend`, `p3`, `auth`
@ -767,7 +746,7 @@ Créer l'écran de création de mot de passe (lien reçu par email).
--- ---
### Ticket #44 : [Frontend] Dashboard Gestionnaire - Structure ### Ticket #42 : [Frontend] Dashboard Gestionnaire - Structure
**Estimation** : 2h **Estimation** : 2h
**Labels** : `frontend`, `p3`, `gestionnaire` **Labels** : `frontend`, `p3`, `gestionnaire`
@ -781,7 +760,7 @@ Créer la structure du dashboard gestionnaire avec 2 onglets.
--- ---
### Ticket #45 : [Frontend] Dashboard Gestionnaire - Liste Parents ### Ticket #43 : [Frontend] Dashboard Gestionnaire - Liste Parents
**Estimation** : 4h **Estimation** : 4h
**Labels** : `frontend`, `p3`, `gestionnaire` **Labels** : `frontend`, `p3`, `gestionnaire`
@ -797,7 +776,7 @@ Créer la liste des parents en attente de validation.
--- ---
### Ticket #46 : [Frontend] Dashboard Gestionnaire - Liste AM ### Ticket #44 : [Frontend] Dashboard Gestionnaire - Liste AM
**Estimation** : 4h **Estimation** : 4h
**Labels** : `frontend`, `p3`, `gestionnaire` **Labels** : `frontend`, `p3`, `gestionnaire`
@ -814,7 +793,7 @@ Créer la liste des assistantes maternelles en attente de validation.
--- ---
### Ticket #47 : [Frontend] Écran Changement MDP Obligatoire ### Ticket #45 : [Frontend] Écran Changement MDP Obligatoire
**Estimation** : 2h **Estimation** : 2h
**Labels** : `frontend`, `p3`, `auth`, `security` **Labels** : `frontend`, `p3`, `auth`, `security`
@ -830,7 +809,7 @@ Créer l'écran de changement de mot de passe obligatoire (première connexion g
--- ---
### Ticket #48 : [Frontend] Gestion Erreurs & Messages ### Ticket #46 : [Frontend] Gestion Erreurs & Messages
**Estimation** : 2h **Estimation** : 2h
**Labels** : `frontend`, `p3`, `ux` **Labels** : `frontend`, `p3`, `ux`
@ -844,7 +823,7 @@ Créer un système de gestion des erreurs et messages utilisateur.
--- ---
### Ticket #49 : [Frontend] Écran Gestion Documents Légaux (Admin) ### Ticket #47 : [Frontend] Écran Gestion Documents Légaux (Admin)
**Estimation** : 5h **Estimation** : 5h
**Labels** : `frontend`, `p3`, `juridique`, `admin` **Labels** : `frontend`, `p3`, `juridique`, `admin`
@ -863,7 +842,7 @@ Créer l'écran de gestion des documents légaux (CGU/Privacy) pour l'admin.
--- ---
### Ticket #50 : [Frontend] Affichage dynamique CGU lors inscription ### Ticket #48 : [Frontend] Affichage dynamique CGU lors inscription
**Estimation** : 2h **Estimation** : 2h
**Labels** : `frontend`, `p3`, `juridique` **Labels** : `frontend`, `p3`, `juridique`
@ -877,24 +856,9 @@ Afficher dynamiquement les CGU/Privacy lors de l'inscription (avec numéro de ve
--- ---
### Ticket #51 : [Frontend] Écran Logs Admin (optionnel v1.1)
**Estimation** : 4h
**Labels** : `frontend`, `p3`, `admin`, `logs`
**Description** :
Créer l'écran de consultation des logs système (optionnel pour v1.1).
**Tâches** :
- [ ] Appel API logs
- [ ] Filtres (date, niveau, utilisateur)
- [ ] Pagination
- [ ] Export CSV
---
## 🔵 PRIORITÉ 4 : Tests & Documentation ## 🔵 PRIORITÉ 4 : Tests & Documentation
### Ticket #52 : [Tests] Tests unitaires Backend ### Ticket #49 : [Tests] Tests unitaires Backend
**Estimation** : 8h **Estimation** : 8h
**Labels** : `tests`, `p4`, `backend` **Labels** : `tests`, `p4`, `backend`

View File

@ -22,9 +22,11 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
final _firstNameController = TextEditingController(); final _firstNameController = TextEditingController();
final _phoneController = TextEditingController(); final _phoneController = TextEditingController();
final _emailController = TextEditingController(); final _emailController = TextEditingController();
final _addressController = TextEditingController(); final _passwordController = TextEditingController();
final _postalCodeController = TextEditingController(); final _confirmPasswordController = TextEditingController();
final _cityController = TextEditingController(); final _addressController = TextEditingController(); // Rue seule
final _postalCodeController = TextEditingController(); // Restauré
final _cityController = TextEditingController(); // Restauré
@override @override
void initState() { void initState() {
@ -46,6 +48,8 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
_lastNameController.text = genLastName; _lastNameController.text = genLastName;
_phoneController.text = DataGenerator.phone(); _phoneController.text = DataGenerator.phone();
_emailController.text = DataGenerator.email(genFirstName, genLastName); _emailController.text = DataGenerator.email(genFirstName, genLastName);
_passwordController.text = DataGenerator.password();
_confirmPasswordController.text = _passwordController.text;
} }
@override @override
@ -54,6 +58,8 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
_firstNameController.dispose(); _firstNameController.dispose();
_phoneController.dispose(); _phoneController.dispose();
_emailController.dispose(); _emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
_addressController.dispose(); _addressController.dispose();
_postalCodeController.dispose(); _postalCodeController.dispose();
_cityController.dispose(); _cityController.dispose();
@ -82,9 +88,9 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// Indicateur d'étape // Indicateur d'étape (à rendre dynamique)
Text( Text(
'Étape 1/6', 'Étape 1/5',
style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -115,43 +121,54 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
const SizedBox(height: 10),
Row( Row(
children: [ children: [
Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Votre nom de famille', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Votre nom de famille', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
Expanded(flex: 1, child: const SizedBox()), Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Votre prénom', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Votre prénom', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
], ],
), ),
const SizedBox(height: 32), const SizedBox(height: 20),
Row( Row(
children: [ children: [
Expanded(flex: 12, child: CustomAppTextField(controller: _phoneController, labelText: 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Votre numéro de téléphone', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _phoneController, labelText: 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Votre numéro de téléphone', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
Expanded(flex: 1, child: const SizedBox()), Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
Expanded(flex: 12, child: CustomAppTextField(controller: _emailController, labelText: 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Votre adresse e-mail', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _emailController, labelText: 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Votre adresse e-mail', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
], ],
), ),
const SizedBox(height: 32), const SizedBox(height: 20),
Row(
children: [
Expanded(flex: 12, child: CustomAppTextField(controller: _passwordController, labelText: 'Mot de passe', obscureText: true, hintText: 'Créez votre mot de passe', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: (value) {
if (value == null || value.isEmpty) return 'Mot de passe requis';
if (value.length < 6) return '6 caractères minimum';
return null;
})),
Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
Expanded(flex: 12, child: CustomAppTextField(controller: _confirmPasswordController, labelText: 'Confirmation', obscureText: true, hintText: 'Confirmez le mot de passe', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: (value) {
if (value == null || value.isEmpty) return 'Confirmation requise';
if (value != _passwordController.text) return 'Ne correspond pas';
return null;
})),
],
),
const SizedBox(height: 20),
CustomAppTextField( CustomAppTextField(
controller: _addressController, controller: _addressController,
labelText: 'Adresse (N° et Rue)', labelText: 'Adresse (N° et Rue)',
hintText: 'Numéro et nom de votre rue', hintText: 'Numéro et nom de votre rue',
style: CustomAppTextFieldStyle.beige, style: CustomAppTextFieldStyle.beige,
fieldWidth: double.infinity, fieldWidth: double.infinity,
labelFontSize: 22.0,
inputFontSize: 20.0,
), ),
const SizedBox(height: 32), const SizedBox(height: 20),
Row( Row(
children: [ children: [
Expanded(flex: 1, child: CustomAppTextField(controller: _postalCodeController, labelText: 'Code Postal', keyboardType: TextInputType.number, hintText: 'Code postal', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 1, child: CustomAppTextField(controller: _postalCodeController, labelText: 'Code Postal', keyboardType: TextInputType.number, hintText: 'Code postal', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
const SizedBox(width: 20), const SizedBox(width: 20),
Expanded(flex: 4, child: CustomAppTextField(controller: _cityController, labelText: 'Ville', hintText: 'Votre ville', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 4, child: CustomAppTextField(controller: _cityController, labelText: 'Ville', hintText: 'Votre ville', style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
], ],
), ),
const SizedBox(height: 10),
], ],
), ),
), ),
@ -188,12 +205,12 @@ class _ParentRegisterStep1ScreenState extends State<ParentRegisterStep1Screen> {
ParentData( ParentData(
firstName: _firstNameController.text, firstName: _firstNameController.text,
lastName: _lastNameController.text, lastName: _lastNameController.text,
address: _addressController.text, address: _addressController.text, // Rue
postalCode: _postalCodeController.text, postalCode: _postalCodeController.text, // Ajout
city: _cityController.text, city: _cityController.text, // Ajout
phone: _phoneController.text, phone: _phoneController.text,
email: _emailController.text, email: _emailController.text,
password: '', // Pas de mot de passe à cette étape password: _passwordController.text,
) )
); );
Navigator.pushNamed(context, '/parent-register/step2', arguments: _registrationData); Navigator.pushNamed(context, '/parent-register/step2', arguments: _registrationData);

View File

@ -22,14 +22,16 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
bool _addParent2 = true; // Pour le test, on ajoute toujours le parent 2 bool _addParent2 = true; // Pour le test, on ajoute toujours le parent 2
bool _sameAddressAsParent1 = false; // Peut être généré aléatoirement aussi bool _sameAddressAsParent1 = false; // Peut être généré aléatoirement aussi
// Contrôleurs pour les champs du parent 2 // Contrôleurs pour les champs du parent 2 (restauration CP et Ville)
final _lastNameController = TextEditingController(); final _lastNameController = TextEditingController();
final _firstNameController = TextEditingController(); final _firstNameController = TextEditingController();
final _phoneController = TextEditingController(); final _phoneController = TextEditingController();
final _emailController = TextEditingController(); final _emailController = TextEditingController();
final _addressController = TextEditingController(); final _passwordController = TextEditingController();
final _postalCodeController = TextEditingController(); final _confirmPasswordController = TextEditingController();
final _cityController = TextEditingController(); final _addressController = TextEditingController(); // Rue seule
final _postalCodeController = TextEditingController(); // Restauré
final _cityController = TextEditingController(); // Restauré
@override @override
void initState() { void initState() {
@ -47,13 +49,17 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
_lastNameController.text = genLastName; _lastNameController.text = genLastName;
_phoneController.text = DataGenerator.phone(); _phoneController.text = DataGenerator.phone();
_emailController.text = DataGenerator.email(genFirstName, genLastName); _emailController.text = DataGenerator.email(genFirstName, genLastName);
_passwordController.text = DataGenerator.password();
_confirmPasswordController.text = _passwordController.text;
_sameAddressAsParent1 = DataGenerator.boolean(); _sameAddressAsParent1 = DataGenerator.boolean();
if (!_sameAddressAsParent1) { if (!_sameAddressAsParent1) {
// Générer adresse, CP, Ville séparément
_addressController.text = DataGenerator.address(); _addressController.text = DataGenerator.address();
_postalCodeController.text = DataGenerator.postalCode(); _postalCodeController.text = DataGenerator.postalCode();
_cityController.text = DataGenerator.city(); _cityController.text = DataGenerator.city();
} else { } else {
// Vider les champs si même adresse (seront désactivés)
_addressController.clear(); _addressController.clear();
_postalCodeController.clear(); _postalCodeController.clear();
_cityController.clear(); _cityController.clear();
@ -66,6 +72,8 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
_firstNameController.dispose(); _firstNameController.dispose();
_phoneController.dispose(); _phoneController.dispose();
_emailController.dispose(); _emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
_addressController.dispose(); _addressController.dispose();
_postalCodeController.dispose(); _postalCodeController.dispose();
_cityController.dispose(); _cityController.dispose();
@ -90,7 +98,7 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text('Étape 2/6', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), Text('Étape 2/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
'Informations du Deuxième Parent (Optionnel)', 'Informations du Deuxième Parent (Optionnel)',
@ -109,9 +117,7 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
const SizedBox(height: 10),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -150,33 +156,40 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
]), ]),
), ),
]), ]),
const SizedBox(height: 32), const SizedBox(height: 25),
Row( Row(
children: [ children: [
Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Nom du parent 2', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _lastNameController, labelText: 'Nom', hintText: 'Nom du parent 2', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
Expanded(flex: 1, child: const SizedBox()), Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Prénom du parent 2', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _firstNameController, labelText: 'Prénom', hintText: 'Prénom du parent 2', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
], ],
), ),
const SizedBox(height: 32), const SizedBox(height: 20),
Row( Row(
children: [ children: [
Expanded(flex: 12, child: CustomAppTextField(controller: _phoneController, labelText: 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Son téléphone', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _phoneController, labelText: 'Téléphone', keyboardType: TextInputType.phone, hintText: 'Son téléphone', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
Expanded(flex: 1, child: const SizedBox()), Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
Expanded(flex: 12, child: CustomAppTextField(controller: _emailController, labelText: 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Son email', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _emailController, labelText: 'Email', keyboardType: TextInputType.emailAddress, hintText: 'Son email', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
], ],
), ),
const SizedBox(height: 32), const SizedBox(height: 20),
CustomAppTextField(controller: _addressController, labelText: 'Adresse (N° et Rue)', hintText: 'Son numéro et nom de rue', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0),
const SizedBox(height: 32),
Row( Row(
children: [ children: [
Expanded(flex: 1, child: CustomAppTextField(controller: _postalCodeController, labelText: 'Code Postal', keyboardType: TextInputType.number, hintText: 'Son code postal', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 12, child: CustomAppTextField(controller: _passwordController, labelText: 'Mot de passe', obscureText: true, hintText: 'Son mot de passe', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: _addParent2 ? (v) => (v == null || v.isEmpty ? 'Requis' : (v.length < 6 ? '6 car. min' : null)) : null)),
Expanded(flex: 1, child: const SizedBox()), // Espace de 4%
Expanded(flex: 12, child: CustomAppTextField(controller: _confirmPasswordController, labelText: 'Confirmation', obscureText: true, hintText: 'Confirmer mot de passe', enabled: _parent2FieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, validator: _addParent2 ? (v) => (v == null || v.isEmpty ? 'Requis' : (v != _passwordController.text ? 'Différent' : null)) : null)),
],
),
const SizedBox(height: 20),
CustomAppTextField(controller: _addressController, labelText: 'Adresse (N° et Rue)', hintText: 'Son numéro et nom de rue', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity),
const SizedBox(height: 20),
Row(
children: [
Expanded(flex: 1, child: CustomAppTextField(controller: _postalCodeController, labelText: 'Code Postal', keyboardType: TextInputType.number, hintText: 'Son code postal', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
const SizedBox(width: 20), const SizedBox(width: 20),
Expanded(flex: 4, child: CustomAppTextField(controller: _cityController, labelText: 'Ville', hintText: 'Sa ville', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity, labelFontSize: 22.0, inputFontSize: 20.0)), Expanded(flex: 4, child: CustomAppTextField(controller: _cityController, labelText: 'Ville', hintText: 'Sa ville', enabled: _addressFieldsEnabled, style: CustomAppTextFieldStyle.beige, fieldWidth: double.infinity)),
], ],
), ),
const SizedBox(height: 10),
], ],
), ),
), ),
@ -212,7 +225,7 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
city: _sameAddressAsParent1 ? _registrationData.parent1.city : _cityController.text, city: _sameAddressAsParent1 ? _registrationData.parent1.city : _cityController.text,
phone: _phoneController.text, phone: _phoneController.text,
email: _emailController.text, email: _emailController.text,
password: '', // Pas de mot de passe à cette étape password: _passwordController.text,
) )
); );
} else { } else {
@ -231,10 +244,8 @@ class _ParentRegisterStep2ScreenState extends State<ParentRegisterStep2Screen> {
void _clearParent2Fields() { void _clearParent2Fields() {
_formKey.currentState?.reset(); _formKey.currentState?.reset();
_lastNameController.clear(); _lastNameController.clear(); _firstNameController.clear(); _phoneController.clear();
_firstNameController.clear(); _emailController.clear(); _passwordController.clear(); _confirmPasswordController.clear();
_phoneController.clear();
_emailController.clear();
_addressController.clear(); _addressController.clear();
_postalCodeController.clear(); _postalCodeController.clear();
_cityController.clear(); _cityController.clear();