192 lines
5.8 KiB
Dart
192 lines
5.8 KiB
Dart
import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
|
|
import 'package:p_tits_pas/models/parent_model.dart';
|
|
import 'package:p_tits_pas/utils/name_format_utils.dart';
|
|
|
|
/// Ligne de liste unifiée dossiers (famille ou AM) — ticket #153.
|
|
enum DossierListType { famille, assistanteMaternelle }
|
|
|
|
class DossierListItem {
|
|
final DossierListType type;
|
|
final String numeroDossier;
|
|
final String libelle;
|
|
final List<String> emails;
|
|
final String? statut;
|
|
|
|
const DossierListItem({
|
|
required this.type,
|
|
required this.numeroDossier,
|
|
required this.libelle,
|
|
this.emails = const [],
|
|
this.statut,
|
|
});
|
|
|
|
bool get isFamille => type == DossierListType.famille;
|
|
bool get isAm => type == DossierListType.assistanteMaternelle;
|
|
|
|
String get typeLabel => isFamille ? 'Famille' : 'AM';
|
|
|
|
/// Sous-titre carte : `NOM Prénom` ou `NOM Prénom - NOM Prénom`.
|
|
String get namesLine => libelle;
|
|
|
|
String get emailsLine => emails.where((e) => e.trim().isNotEmpty).join(' · ');
|
|
|
|
/// Titre carte : numéro de dossier seul.
|
|
String get titleLine => numeroDossier;
|
|
|
|
bool matchesQuery(String query) {
|
|
final q = query.trim().toLowerCase();
|
|
if (q.isEmpty) return true;
|
|
if (numeroDossier.toLowerCase().contains(q)) return true;
|
|
if (libelle.toLowerCase().contains(q)) return true;
|
|
for (final e in emails) {
|
|
if (e.toLowerCase().contains(q)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Une ligne par `numero_dossier` (foyer dédupliqué).
|
|
static List<DossierListItem> fromParents(List<ParentModel> parents) {
|
|
final byDossier = <String, List<ParentModel>>{};
|
|
for (final p in parents) {
|
|
final num = (p.user.numeroDossier ?? '').trim();
|
|
if (num.isEmpty) continue;
|
|
byDossier.putIfAbsent(num, () => []).add(p);
|
|
}
|
|
|
|
final items = <DossierListItem>[];
|
|
for (final entry in byDossier.entries) {
|
|
final seenIds = <String>{};
|
|
final names = <String>[];
|
|
final emails = <String>[];
|
|
final statuts = <String>[];
|
|
|
|
void consider(
|
|
String? id,
|
|
String? nom,
|
|
String? prenom,
|
|
String? email,
|
|
String? statut,
|
|
) {
|
|
final uid = (id ?? '').trim();
|
|
if (uid.isEmpty || !seenIds.add(uid)) return;
|
|
final label = formatDossierPersonLabel(
|
|
nom: nom,
|
|
prenom: prenom,
|
|
email: email,
|
|
);
|
|
if (label.isNotEmpty) names.add(label);
|
|
final e = (email ?? '').trim();
|
|
if (e.isNotEmpty) emails.add(e);
|
|
final s = (statut ?? '').trim();
|
|
if (s.isNotEmpty) statuts.add(s);
|
|
}
|
|
|
|
for (final p in entry.value) {
|
|
consider(
|
|
p.user.id,
|
|
p.user.nom,
|
|
p.user.prenom,
|
|
p.user.email,
|
|
p.user.statut,
|
|
);
|
|
final co = p.coParent;
|
|
if (co != null) {
|
|
consider(co.id, co.nom, co.prenom, co.email, co.statut);
|
|
}
|
|
}
|
|
|
|
items.add(
|
|
DossierListItem(
|
|
type: DossierListType.famille,
|
|
numeroDossier: entry.key,
|
|
libelle: names.isNotEmpty ? names.join(' - ') : 'Famille',
|
|
emails: emails,
|
|
statut: _preferStatut(statuts),
|
|
),
|
|
);
|
|
}
|
|
return items;
|
|
}
|
|
|
|
static List<DossierListItem> fromAssistantes(
|
|
List<AssistanteMaternelleModel> ams,
|
|
) {
|
|
final byDossier = <String, AssistanteMaternelleModel>{};
|
|
for (final am in ams) {
|
|
final num = (am.user.numeroDossier ?? '').trim();
|
|
if (num.isEmpty) continue;
|
|
byDossier.putIfAbsent(num, () => am);
|
|
}
|
|
|
|
return byDossier.entries.map((e) {
|
|
final u = e.value.user;
|
|
final name = formatDossierPersonLabel(
|
|
nom: u.nom,
|
|
prenom: u.prenom,
|
|
email: u.email,
|
|
);
|
|
return DossierListItem(
|
|
type: DossierListType.assistanteMaternelle,
|
|
numeroDossier: e.key,
|
|
libelle: name.isNotEmpty ? name : 'AM',
|
|
emails: u.email.trim().isEmpty ? const [] : [u.email.trim()],
|
|
statut: u.statut?.trim(),
|
|
);
|
|
}).toList();
|
|
}
|
|
|
|
/// Priorité affichage : en_attente > suspendu > refuse > actif > autre.
|
|
static String? _preferStatut(List<String> raw) {
|
|
if (raw.isEmpty) return null;
|
|
const order = ['en_attente', 'suspendu', 'refuse', 'actif'];
|
|
for (final wanted in order) {
|
|
for (final s in raw) {
|
|
if (s.toLowerCase() == wanted) return s;
|
|
}
|
|
}
|
|
return raw.first;
|
|
}
|
|
}
|
|
|
|
/// Affichage carte dossier : `NOM Prénom` (repli email).
|
|
String formatDossierPersonLabel({
|
|
String? nom,
|
|
String? prenom,
|
|
String? email,
|
|
}) {
|
|
final n = (nom ?? '').trim().toUpperCase();
|
|
final p = formatPersonNameCase(prenom ?? '');
|
|
if (n.isNotEmpty && p.isNotEmpty) return '$n $p';
|
|
if (n.isNotEmpty) return n;
|
|
if (p.isNotEmpty) return p;
|
|
return (email ?? '').trim();
|
|
}
|
|
|
|
/// Reformate un libellé famille API (`A & B` / `Famille …`) en `NOM Prénom - …`.
|
|
String formatDossierFamilyNamesLine(String libelle) {
|
|
var raw = libelle.trim();
|
|
if (raw.isEmpty) return '';
|
|
raw = raw.replaceFirst(RegExp(r'^famille\s+', caseSensitive: false), '');
|
|
raw = raw
|
|
.replaceAll(RegExp(r'\s+&\s+'), ' - ')
|
|
.replaceAll(RegExp(r'\s+et\s+', caseSensitive: false), ' - ');
|
|
final parts = raw
|
|
.split(RegExp(r'\s+-\s+'))
|
|
.map((part) => _formatLoosePersonSegment(part.trim()))
|
|
.where((s) => s.isNotEmpty)
|
|
.toList();
|
|
return parts.join(' - ');
|
|
}
|
|
|
|
/// Segment libre type « martin sophie » ou « DURAND Amélie » → `NOM Prénom`.
|
|
String _formatLoosePersonSegment(String segment) {
|
|
final words =
|
|
segment.split(RegExp(r'\s+')).where((w) => w.isNotEmpty).toList();
|
|
if (words.isEmpty) return '';
|
|
if (words.length == 1) return words.first.toUpperCase();
|
|
// Convention affichage : premier mot = NOM, reste = prénom(s).
|
|
final nom = words.first.toUpperCase();
|
|
final prenom = formatPersonNameCase(words.sublist(1).join(' '));
|
|
return '$nom $prenom';
|
|
}
|