import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:p_tits_pas/utils/enfant_status_utils.dart'; /// Affiche une date ISO / parseable en `dd/MM/yyyy`, avec repli sur la chaîne ou [ifEmpty]. String formatIsoDateFr(String? s, {String ifEmpty = '–'}) { if (s == null || s.trim().isEmpty) return ifEmpty; try { return DateFormat('dd/MM/yyyy').format(DateTime.parse(s.trim())); } catch (_) { return s.trim(); } } /// Affiche une date ISO en saisie guidée `jj / mm / aaaa`. String formatIsoDateFrInput(String? s, {String ifEmpty = ''}) { if (s == null || s.trim().isEmpty) return ifEmpty; try { final dt = DateTime.parse(s.trim()); return formatFrenchDateDigits( DateFormat('ddMMyyyy').format(dt), ); } catch (_) { return formatFrenchDateDigits(s.replaceAll(RegExp(r'\D'), '')); } } /// Formate jusqu’à 8 chiffres en `jj / mm / aaaa`. String formatFrenchDateDigits(String digits) { final d = digits.replaceAll(RegExp(r'\D'), ''); final limited = d.length > 8 ? d.substring(0, 8) : d; final buf = StringBuffer(); for (var i = 0; i < limited.length; i++) { if (i == 2 || i == 4) buf.write(' / '); buf.write(limited[i]); } return buf.toString(); } /// Convertit `dd/MM/yyyy`, `dd / MM / yyyy`, 8 chiffres ou ISO en `yyyy-MM-dd`. String? parseFrDateToIso(String text) { final t = text.trim(); if (t.isEmpty) return null; final digits = t.replaceAll(RegExp(r'\D'), ''); if (digits.length == 8) { final dd = digits.substring(0, 2); final mm = digits.substring(2, 4); final yyyy = digits.substring(4, 8); try { return DateFormat('dd/MM/yyyy') .parseStrict('$dd/$mm/$yyyy') .toIso8601String() .split('T') .first; } catch (_) {} } try { return DateFormat('dd/MM/yyyy') .parseStrict(t) .toIso8601String() .split('T') .first; } catch (_) { try { return DateTime.parse(t).toIso8601String().split('T').first; } catch (_) { return null; } } } ({int years, int months, int days}) computeChildAgeParts( DateTime birth, DateTime reference, ) { final birthDay = DateTime(birth.year, birth.month, birth.day); final refDay = DateTime(reference.year, reference.month, reference.day); var years = refDay.year - birthDay.year; var months = refDay.month - birthDay.month; var days = refDay.day - birthDay.day; if (days < 0) { months--; final prevMonth = DateTime(refDay.year, refDay.month, 0); days += prevMonth.day; } if (months < 0) { years--; months += 12; } return (years: years, months: months, days: days); } String _yearLabel(int years) => years == 1 ? '1 an' : '$years ans'; String _monthLabel(int months) => months == 1 ? '1 mois' : '$months mois'; /// Libellé d'âge pour un enfant (liste admin, fiche parent, etc.). String formatChildAgeLabel({ String? birthDate, String? dueDate, String? status, }) { if (status == 'a_naitre' || normalizeEnfantStatus(status) == 'a_naitre') { final due = formatIsoDateFr(dueDate, ifEmpty: ''); if (due.isNotEmpty) return 'Naissance prévue : $due'; return 'À naître'; } if (birthDate == null || birthDate.trim().isEmpty) return ''; try { final birth = DateTime.parse(birthDate.trim().split('T').first); final now = DateTime.now(); final parts = computeChildAgeParts(birth, now); if (parts.years >= 1) { if (parts.months > 0) { return 'Âge : ${_yearLabel(parts.years)} ${parts.months} mois'; } return 'Âge : ${_yearLabel(parts.years)}'; } if (parts.months >= 1) { return 'Âge : ${_monthLabel(parts.months)}'; } final totalDays = DateTime(now.year, now.month, now.day) .difference(DateTime(birth.year, birth.month, birth.day)) .inDays; if (totalDays <= 0) return 'Âge : né récemment'; return 'Âge : $totalDays jour${totalDays > 1 ? 's' : ''}'; } catch (_) { return 'Né le ${formatIsoDateFr(birthDate)}'; } } /// Saisie date FR : 8 chiffres → `jj / mm / aaaa`. class FrenchDateInputFormatter extends TextInputFormatter { const FrenchDateInputFormatter(); @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { final digits = newValue.text.replaceAll(RegExp(r'\D'), ''); final limited = digits.length > 8 ? digits.substring(0, 8) : digits; final formatted = formatFrenchDateDigits(limited); final digitsBeforeCursor = newValue.text .substring(0, newValue.selection.start.clamp(0, newValue.text.length)) .replaceAll(RegExp(r'\D'), '') .length .clamp(0, limited.length); return TextEditingValue( text: formatted, selection: TextSelection.collapsed( offset: _cursorOffset(formatted, digitsBeforeCursor), ), ); } static int _cursorOffset(String formatted, int digitsBeforeCursor) { if (digitsBeforeCursor <= 0) return 0; var seen = 0; for (var i = 0; i < formatted.length; i++) { if (RegExp(r'\d').hasMatch(formatted[i])) { seen++; if (seen >= digitsBeforeCursor) return i + 1; } } return formatted.length; } }