feat(frontend): NIR 15 car., formatage, validation, widget dédié (#102)
- nir_utils: normalizeNir, formatNir, validateNir (format + clé), Corse 2A/2B - NirInputFormatter: formatage auto à la saisie (espaces + tiret) - NirTextField: widget réutilisable pour champ NIR - professional_info_form_screen: NIR 15 car., affichage formaté à l'init - custom_app_text_field: paramètre inputFormatters Refs: #102 Made-with: Cursor
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
@@ -6,7 +7,9 @@ import 'dart:math' as math;
|
||||
import 'dart:io';
|
||||
import '../models/card_assets.dart';
|
||||
import '../config/display_config.dart';
|
||||
import '../utils/nir_utils.dart';
|
||||
import 'custom_app_text_field.dart';
|
||||
import 'nir_text_field.dart';
|
||||
import 'form_field_wrapper.dart';
|
||||
import 'app_custom_checkbox.dart';
|
||||
import 'hover_relief_widget.dart';
|
||||
@@ -97,7 +100,8 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
: '';
|
||||
_birthCityController.text = data.birthCity;
|
||||
_birthCountryController.text = data.birthCountry;
|
||||
_nirController.text = data.nir;
|
||||
final nirRaw = nirToRaw(data.nir);
|
||||
_nirController.text = nirRaw.length == 15 ? formatNir(nirRaw) : data.nir;
|
||||
_agrementController.text = data.agrementNumber;
|
||||
_capacityController.text = data.capacity?.toString() ?? '';
|
||||
_photoPathFramework = data.photoPath;
|
||||
@@ -161,7 +165,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
dateOfBirth: _selectedDate,
|
||||
birthCity: _birthCityController.text,
|
||||
birthCountry: _birthCountryController.text,
|
||||
nir: _nirController.text,
|
||||
nir: normalizeNir(_nirController.text),
|
||||
agrementNumber: _agrementController.text,
|
||||
capacity: int.tryParse(_capacityController.text),
|
||||
);
|
||||
@@ -499,7 +503,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
children: [
|
||||
Expanded(flex: 2, child: _buildReadonlyField('Date de naissance', _dateOfBirthController.text)),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(flex: 3, child: _buildReadonlyField('NIR', _nirController.text)),
|
||||
Expanded(flex: 3, child: _buildReadonlyField('NIR', _formatNirForDisplay(_nirController.text))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -525,6 +529,12 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/// NIR formaté pour affichage (1 12 34 56 789 012-34 ou 2A pour la Corse).
|
||||
String _formatNirForDisplay(String value) {
|
||||
final raw = nirToRaw(value);
|
||||
return raw.length == 15 ? formatNir(raw) : value;
|
||||
}
|
||||
|
||||
/// Helper pour champ Readonly style "Beige"
|
||||
Widget _buildReadonlyField(String label, String value) {
|
||||
return Column(
|
||||
@@ -609,18 +619,12 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
],
|
||||
),
|
||||
SizedBox(height: verticalSpacing),
|
||||
_buildField(
|
||||
config: config,
|
||||
label: 'N° Sécurité Sociale (NIR)',
|
||||
NirTextField(
|
||||
controller: _nirController,
|
||||
hint: 'Votre NIR à 13 chiffres',
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return 'NIR requis';
|
||||
if (v.length != 13) return 'Le NIR doit contenir 13 chiffres';
|
||||
if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3';
|
||||
return null;
|
||||
},
|
||||
fieldWidth: double.infinity,
|
||||
fieldHeight: config.isMobile ? 45.0 : 53.0,
|
||||
labelFontSize: config.isMobile ? 15.0 : 22.0,
|
||||
inputFontSize: config.isMobile ? 14.0 : 20.0,
|
||||
),
|
||||
SizedBox(height: verticalSpacing),
|
||||
Row(
|
||||
@@ -695,18 +699,12 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildField(
|
||||
config: config,
|
||||
label: 'N° Sécurité Sociale (NIR)',
|
||||
NirTextField(
|
||||
controller: _nirController,
|
||||
hint: 'Votre NIR à 13 chiffres',
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return 'NIR requis';
|
||||
if (v.length != 13) return 'Le NIR doit contenir 13 chiffres';
|
||||
if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3';
|
||||
return null;
|
||||
},
|
||||
fieldWidth: double.infinity,
|
||||
fieldHeight: 45.0,
|
||||
labelFontSize: 15.0,
|
||||
inputFontSize: 14.0,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -796,6 +794,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
VoidCallback? onTap,
|
||||
IconData? suffixIcon,
|
||||
String? Function(String?)? validator,
|
||||
List<TextInputFormatter>? inputFormatters,
|
||||
}) {
|
||||
if (config.isReadonly) {
|
||||
return FormFieldWrapper(
|
||||
@@ -817,6 +816,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
onTap: onTap,
|
||||
suffixIcon: suffixIcon,
|
||||
validator: validator,
|
||||
inputFormatters: inputFormatters,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user