feat(frontend): Ajout champ genre obligatoire + Clean Code Step3

- Ajout champ gender (H/F) dans ChildData avec sélecteur radio
- Correction indicateur étape: 3/5 → 3/6
- Suppression commentaires inutiles et code obsolète
- Suppression print() pour gestion silencieuse des erreurs
- Ajout méthode gender() dans DataGenerator

Ticket #38
This commit is contained in:
MARTIN Julien 2025-12-02 14:43:10 +01:00
parent 1bbdab03d0
commit aee061f9bd
3 changed files with 59 additions and 32 deletions

View File

@ -28,22 +28,24 @@ class ParentData {
class ChildData { class ChildData {
String firstName; String firstName;
String lastName; String lastName;
String dob; // Date de naissance ou prévisionnelle String dob;
String gender;
bool photoConsent; bool photoConsent;
bool multipleBirth; bool multipleBirth;
bool isUnbornChild; bool isUnbornChild;
File? imageFile; File? imageFile;
CardColorVertical cardColor; // Nouveau champ pour la couleur de la carte CardColorVertical cardColor;
ChildData({ ChildData({
this.firstName = '', this.firstName = '',
this.lastName = '', this.lastName = '',
this.dob = '', this.dob = '',
this.gender = 'F',
this.photoConsent = false, this.photoConsent = false,
this.multipleBirth = false, this.multipleBirth = false,
this.isUnbornChild = false, this.isUnbornChild = false,
this.imageFile, this.imageFile,
required this.cardColor, // Rendre requis dans le constructeur required this.cardColor,
}); });
} }

View File

@ -1,19 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'dart:math' as math; // Pour la rotation du chevron import 'dart:math' as math;
import 'package:flutter/gestures.dart'; // Pour PointerDeviceKind import 'package:flutter/gestures.dart';
import '../../widgets/hover_relief_widget.dart'; // Import du nouveau widget import '../../widgets/hover_relief_widget.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
// import 'package:image_cropper/image_cropper.dart'; // Supprimé import 'dart:io' show File, Platform;
import 'dart:io' show File, Platform; // Ajout de Platform import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/foundation.dart' show kIsWeb; // Import pour kIsWeb import '../../widgets/custom_app_text_field.dart';
import '../../widgets/custom_app_text_field.dart'; // Import du nouveau widget TextField import '../../widgets/app_custom_checkbox.dart';
import '../../widgets/app_custom_checkbox.dart'; // Import du nouveau widget Checkbox import '../../models/user_registration_data.dart';
import '../../models/user_registration_data.dart'; // Import du modèle de données import '../../utils/data_generator.dart';
import '../../utils/data_generator.dart'; // Import du générateur import '../../models/card_assets.dart';
import '../../models/card_assets.dart'; // Import des enums de cartes
// La classe _ChildFormData est supprimée car on utilise ChildData du modèle
class ParentRegisterStep3Screen extends StatefulWidget { class ParentRegisterStep3Screen extends StatefulWidget {
final UserRegistrationData registrationData; // Accepte les données final UserRegistrationData registrationData; // Accepte les données
@ -25,16 +22,15 @@ class ParentRegisterStep3Screen extends StatefulWidget {
} }
class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> { class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
late UserRegistrationData _registrationData; // Stocke l'état complet late UserRegistrationData _registrationData;
final ScrollController _scrollController = ScrollController(); // Pour le défilement horizontal final ScrollController _scrollController = ScrollController();
bool _isScrollable = false; bool _isScrollable = false;
bool _showLeftFade = false; bool _showLeftFade = false;
bool _showRightFade = false; bool _showRightFade = false;
static const double _fadeExtent = 0.05; // Pourcentage de fondu static const double _fadeExtent = 0.05;
// Liste ordonnée des couleurs de cartes pour les enfants
static const List<CardColorVertical> _childCardColors = [ static const List<CardColorVertical> _childCardColors = [
CardColorVertical.lavender, // Premier enfant toujours lavande CardColorVertical.lavender,
CardColorVertical.pink, CardColorVertical.pink,
CardColorVertical.peach, CardColorVertical.peach,
CardColorVertical.lime, CardColorVertical.lime,
@ -43,11 +39,7 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
CardColorVertical.blue, CardColorVertical.blue,
]; ];
// Garder une trace des couleurs déjà utilisées final Set<CardColorVertical> _usedColors = {};
final Set<CardColorVertical> _usedColors = {};
// Utilisation de GlobalKey pour les cartes enfants si validation complexe future
// Map<int, GlobalKey<FormState>> _childFormKeys = {};
@override @override
void initState() { void initState() {
@ -101,6 +93,7 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
lastName: _registrationData.parent1.lastName, lastName: _registrationData.parent1.lastName,
firstName: DataGenerator.firstName(), firstName: DataGenerator.firstName(),
dob: DataGenerator.dob(isUnborn: isUnborn), dob: DataGenerator.dob(isUnborn: isUnborn),
gender: DataGenerator.gender(),
isUnbornChild: isUnborn, isUnbornChild: isUnborn,
photoConsent: DataGenerator.boolean(), photoConsent: DataGenerator.boolean(),
multipleBirth: DataGenerator.boolean(), multipleBirth: DataGenerator.boolean(),
@ -138,8 +131,10 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
_registrationData.children[childIndex].imageFile = File(pickedFile.path); _registrationData.children[childIndex].imageFile = File(pickedFile.path);
} }
}); });
} }
} catch (e) { print("Erreur image: $e"); } } catch (e) {
// Gestion silencieuse de l'erreur
}
} }
Future<void> _selectDate(BuildContext context, int childIndex) async { Future<void> _selectDate(BuildContext context, int childIndex) async {
@ -194,7 +189,7 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text('Étape 3/5', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)), Text('Étape 3/6', style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54)),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
'Informations Enfants', 'Informations Enfants',
@ -228,18 +223,18 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
return Padding( return Padding(
padding: const EdgeInsets.only(right: 20.0), padding: const EdgeInsets.only(right: 20.0),
child: _ChildCardWidget( child: _ChildCardWidget(
key: ValueKey(_registrationData.children[index].hashCode), // Utiliser une clé basée sur les données key: ValueKey(_registrationData.children[index].hashCode),
childData: _registrationData.children[index], childData: _registrationData.children[index],
childIndex: index, childIndex: index,
onPickImage: () => _pickImage(index), onPickImage: () => _pickImage(index),
onDateSelect: () => _selectDate(context, index), onDateSelect: () => _selectDate(context, index),
onFirstNameChanged: (value) => setState(() => _registrationData.children[index].firstName = value), onFirstNameChanged: (value) => setState(() => _registrationData.children[index].firstName = value),
onLastNameChanged: (value) => setState(() => _registrationData.children[index].lastName = value), onLastNameChanged: (value) => setState(() => _registrationData.children[index].lastName = value),
onGenderChanged: (value) => setState(() => _registrationData.children[index].gender = value),
onTogglePhotoConsent: (newValue) => setState(() => _registrationData.children[index].photoConsent = newValue), onTogglePhotoConsent: (newValue) => setState(() => _registrationData.children[index].photoConsent = newValue),
onToggleMultipleBirth: (newValue) => setState(() => _registrationData.children[index].multipleBirth = newValue), onToggleMultipleBirth: (newValue) => setState(() => _registrationData.children[index].multipleBirth = newValue),
onToggleIsUnborn: (newValue) => setState(() { onToggleIsUnborn: (newValue) => setState(() {
_registrationData.children[index].isUnbornChild = newValue; _registrationData.children[index].isUnbornChild = newValue;
// Générer une nouvelle date si on change le statut
_registrationData.children[index].dob = DataGenerator.dob(isUnborn: newValue); _registrationData.children[index].dob = DataGenerator.dob(isUnborn: newValue);
}), }),
onRemove: () => _removeChild(index), onRemove: () => _removeChild(index),
@ -295,13 +290,14 @@ class _ParentRegisterStep3ScreenState extends State<ParentRegisterStep3Screen> {
} }
// Widget pour la carte enfant (adapté pour prendre ChildData et des callbacks) // Widget pour la carte enfant (adapté pour prendre ChildData et des callbacks)
class _ChildCardWidget extends StatefulWidget { // Transformé en StatefulWidget pour gérer les contrôleurs internes class _ChildCardWidget extends StatefulWidget {
final ChildData childData; final ChildData childData;
final int childIndex; final int childIndex;
final VoidCallback onPickImage; final VoidCallback onPickImage;
final VoidCallback onDateSelect; final VoidCallback onDateSelect;
final ValueChanged<String> onFirstNameChanged; final ValueChanged<String> onFirstNameChanged;
final ValueChanged<String> onLastNameChanged; final ValueChanged<String> onLastNameChanged;
final ValueChanged<String> onGenderChanged;
final ValueChanged<bool> onTogglePhotoConsent; final ValueChanged<bool> onTogglePhotoConsent;
final ValueChanged<bool> onToggleMultipleBirth; final ValueChanged<bool> onToggleMultipleBirth;
final ValueChanged<bool> onToggleIsUnborn; final ValueChanged<bool> onToggleIsUnborn;
@ -316,6 +312,7 @@ class _ChildCardWidget extends StatefulWidget { // Transformé en StatefulWidget
required this.onDateSelect, required this.onDateSelect,
required this.onFirstNameChanged, required this.onFirstNameChanged,
required this.onLastNameChanged, required this.onLastNameChanged,
required this.onGenderChanged,
required this.onTogglePhotoConsent, required this.onTogglePhotoConsent,
required this.onToggleMultipleBirth, required this.onToggleMultipleBirth,
required this.onToggleIsUnborn, required this.onToggleIsUnborn,
@ -436,6 +433,32 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> {
fieldHeight: 55.0 * 1.1, // 60.5 fieldHeight: 55.0 * 1.1, // 60.5
), ),
const SizedBox(height: 9.0 * 1.1), // 9.9 const SizedBox(height: 9.0 * 1.1), // 9.9
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Genre', style: GoogleFonts.merienda(fontSize: 16 * 1.1, fontWeight: FontWeight.w600)),
Row(
children: [
Radio<String>(
value: 'H',
groupValue: widget.childData.gender,
onChanged: (value) => widget.onGenderChanged(value!),
activeColor: Theme.of(context).primaryColor,
),
Text('H', style: GoogleFonts.merienda(fontSize: 16 * 1.1)),
const SizedBox(width: 20),
Radio<String>(
value: 'F',
groupValue: widget.childData.gender,
onChanged: (value) => widget.onGenderChanged(value!),
activeColor: Theme.of(context).primaryColor,
),
Text('F', style: GoogleFonts.merienda(fontSize: 16 * 1.1)),
],
),
],
),
const SizedBox(height: 9.0 * 1.1), // 9.9
CustomAppTextField( CustomAppTextField(
controller: _dobController, controller: _dobController,
labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance', labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',

View File

@ -51,6 +51,8 @@ class DataGenerator {
static bool boolean() => _random.nextBool(); static bool boolean() => _random.nextBool();
static String gender() => _random.nextBool() ? 'H' : 'F';
static String motivation() { static String motivation() {
int count = _random.nextInt(3) + 2; // 2 à 4 phrases int count = _random.nextInt(3) + 2; // 2 à 4 phrases
List<String> chosenSnippets = []; List<String> chosenSnippets = [];