feat(admin): onglet À valider, dossier unifié et modales de validation
- Onglet « À valider » (AM + familles), pending-families et détail dossier par numéro. - Wizards validation AM et famille, modale commune, chargement via GET /dossiers/:num. - UI : cartes enfants, photo AM (cadre uniforme, ratio), NIR affiché formaté (espaces autour du tiret). - Backend : DTO / routes parents pending ; scripts de test et mise à jour issue Gitea. Tickets #107, #119. Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Format français : 10 chiffres affichés par paires (ex. 06 12 34 56 78).
|
||||
|
||||
/// Ne garde que les chiffres (max 10).
|
||||
String normalizePhone(String raw) {
|
||||
final digits = raw.replaceAll(RegExp(r'\D'), '');
|
||||
return digits.length > 10 ? digits.substring(0, 10) : digits;
|
||||
}
|
||||
|
||||
/// Retourne le numéro formaté pour l'affichage (ex. "06 12 34 56 78").
|
||||
/// Si [raw] est vide après normalisation, retourne [raw] tel quel (pour afficher "–" etc.).
|
||||
String formatPhoneForDisplay(String raw) {
|
||||
if (raw.trim().isEmpty) return raw;
|
||||
final normalized = normalizePhone(raw);
|
||||
if (normalized.isEmpty) return raw;
|
||||
final buffer = StringBuffer();
|
||||
for (var i = 0; i < normalized.length; i++) {
|
||||
if (i > 0 && i.isEven) buffer.write(' ');
|
||||
buffer.write(normalized[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// Formatter de saisie : uniquement chiffres, espaces automatiques toutes les 2 chiffres, max 10 chiffres.
|
||||
class FrenchPhoneNumberFormatter extends TextInputFormatter {
|
||||
const FrenchPhoneNumberFormatter();
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
final digits = newValue.text.replaceAll(RegExp(r'\D'), '');
|
||||
final normalized = digits.length > 10 ? digits.substring(0, 10) : digits;
|
||||
final buffer = StringBuffer();
|
||||
for (var i = 0; i < normalized.length; i++) {
|
||||
if (i > 0 && i.isEven) buffer.write(' ');
|
||||
buffer.write(normalized[i]);
|
||||
}
|
||||
final formatted = buffer.toString();
|
||||
|
||||
// Conserver la position du curseur : compter les chiffres avant la sélection
|
||||
final sel = newValue.selection;
|
||||
final digitsBeforeCursor = newValue.text
|
||||
.substring(0, sel.start.clamp(0, newValue.text.length))
|
||||
.replaceAll(RegExp(r'\D'), '')
|
||||
.length;
|
||||
final newOffset = digitsBeforeCursor + (digitsBeforeCursor > 0 ? digitsBeforeCursor ~/ 2 : 0);
|
||||
final clampedOffset = newOffset.clamp(0, formatted.length);
|
||||
|
||||
return TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: clampedOffset),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user