/// Modèle pour les critères de recherche avancée des membres /// Correspond au DTO Java MembreSearchCriteria class MembreSearchCriteria { /// Terme de recherche général (nom, prénom, email) final String? query; /// Recherche par nom exact ou partiel final String? nom; /// Recherche par prénom exact ou partiel final String? prenom; /// Recherche par email exact ou partiel final String? email; /// Filtre par numéro de téléphone final String? telephone; /// Liste des IDs d'organisations final List? organisationIds; /// Liste des rôles à rechercher final List? roles; /// Filtre par statut d'activité final String? statut; /// Date d'adhésion minimum (format ISO 8601) final String? dateAdhesionMin; /// Date d'adhésion maximum (format ISO 8601) final String? dateAdhesionMax; /// Âge minimum final int? ageMin; /// Âge maximum final int? ageMax; /// Filtre par région final String? region; /// Filtre par ville final String? ville; /// Filtre par profession final String? profession; /// Filtre par nationalité final String? nationalite; /// Filtre membres du bureau uniquement final bool? membreBureau; /// Filtre responsables uniquement final bool? responsable; /// Inclure les membres inactifs dans la recherche final bool includeInactifs; const MembreSearchCriteria({ this.query, this.nom, this.prenom, this.email, this.telephone, this.organisationIds, this.roles, this.statut, this.dateAdhesionMin, this.dateAdhesionMax, this.ageMin, this.ageMax, this.region, this.ville, this.profession, this.nationalite, this.membreBureau, this.responsable, this.includeInactifs = false, }); /// Factory constructor pour créer depuis JSON factory MembreSearchCriteria.fromJson(Map json) { return MembreSearchCriteria( query: json['query'] as String?, nom: json['nom'] as String?, prenom: json['prenom'] as String?, email: json['email'] as String?, telephone: json['telephone'] as String?, organisationIds: (json['organisationIds'] as List?)?.cast(), roles: (json['roles'] as List?)?.cast(), statut: json['statut'] as String?, dateAdhesionMin: json['dateAdhesionMin'] as String?, dateAdhesionMax: json['dateAdhesionMax'] as String?, ageMin: json['ageMin'] as int?, ageMax: json['ageMax'] as int?, region: json['region'] as String?, ville: json['ville'] as String?, profession: json['profession'] as String?, nationalite: json['nationalite'] as String?, membreBureau: json['membreBureau'] as bool?, responsable: json['responsable'] as bool?, includeInactifs: json['includeInactifs'] as bool? ?? false, ); } /// Convertit vers JSON Map toJson() { return { 'query': query, 'nom': nom, 'prenom': prenom, 'email': email, 'telephone': telephone, 'organisationIds': organisationIds, 'roles': roles, 'statut': statut, 'dateAdhesionMin': dateAdhesionMin, 'dateAdhesionMax': dateAdhesionMax, 'ageMin': ageMin, 'ageMax': ageMax, 'region': region, 'ville': ville, 'profession': profession, 'nationalite': nationalite, 'membreBureau': membreBureau, 'responsable': responsable, 'includeInactifs': includeInactifs, }; } /// Vérifie si au moins un critère de recherche est défini bool get hasAnyCriteria { return query?.isNotEmpty == true || nom?.isNotEmpty == true || prenom?.isNotEmpty == true || email?.isNotEmpty == true || telephone?.isNotEmpty == true || organisationIds?.isNotEmpty == true || roles?.isNotEmpty == true || statut?.isNotEmpty == true || dateAdhesionMin?.isNotEmpty == true || dateAdhesionMax?.isNotEmpty == true || ageMin != null || ageMax != null || region?.isNotEmpty == true || ville?.isNotEmpty == true || profession?.isNotEmpty == true || nationalite?.isNotEmpty == true || membreBureau != null || responsable != null; } /// Valide la cohérence des critères de recherche bool get isValid { // Validation des âges if (ageMin != null && ageMax != null) { if (ageMin! > ageMax!) { return false; } } // Validation des dates (si implémentée) if (dateAdhesionMin != null && dateAdhesionMax != null) { try { final dateMin = DateTime.parse(dateAdhesionMin!); final dateMax = DateTime.parse(dateAdhesionMax!); if (dateMin.isAfter(dateMax)) { return false; } } catch (e) { return false; } } return true; } /// Retourne une description textuelle des critères actifs String get description { final parts = []; if (query?.isNotEmpty == true) parts.add("Recherche: '$query'"); if (nom?.isNotEmpty == true) parts.add("Nom: '$nom'"); if (prenom?.isNotEmpty == true) parts.add("Prénom: '$prenom'"); if (email?.isNotEmpty == true) parts.add("Email: '$email'"); if (statut?.isNotEmpty == true) parts.add("Statut: $statut"); if (organisationIds?.isNotEmpty == true) { parts.add("Organisations: ${organisationIds!.length}"); } if (roles?.isNotEmpty == true) { parts.add("Rôles: ${roles!.join(', ')}"); } if (dateAdhesionMin?.isNotEmpty == true) { parts.add("Adhésion >= $dateAdhesionMin"); } if (dateAdhesionMax?.isNotEmpty == true) { parts.add("Adhésion <= $dateAdhesionMax"); } if (ageMin != null) parts.add("Âge >= $ageMin"); if (ageMax != null) parts.add("Âge <= $ageMax"); if (region?.isNotEmpty == true) parts.add("Région: '$region'"); if (ville?.isNotEmpty == true) parts.add("Ville: '$ville'"); if (profession?.isNotEmpty == true) parts.add("Profession: '$profession'"); if (nationalite?.isNotEmpty == true) parts.add("Nationalité: '$nationalite'"); if (membreBureau == true) parts.add("Membre bureau"); if (responsable == true) parts.add("Responsable"); return parts.join(' • '); } /// Crée une copie avec des modifications MembreSearchCriteria copyWith({ String? query, String? nom, String? prenom, String? email, String? telephone, List? organisationIds, List? roles, String? statut, String? dateAdhesionMin, String? dateAdhesionMax, int? ageMin, int? ageMax, String? region, String? ville, String? profession, String? nationalite, bool? membreBureau, bool? responsable, bool? includeInactifs, }) { return MembreSearchCriteria( query: query ?? this.query, nom: nom ?? this.nom, prenom: prenom ?? this.prenom, email: email ?? this.email, telephone: telephone ?? this.telephone, organisationIds: organisationIds ?? this.organisationIds, roles: roles ?? this.roles, statut: statut ?? this.statut, dateAdhesionMin: dateAdhesionMin ?? this.dateAdhesionMin, dateAdhesionMax: dateAdhesionMax ?? this.dateAdhesionMax, ageMin: ageMin ?? this.ageMin, ageMax: ageMax ?? this.ageMax, region: region ?? this.region, ville: ville ?? this.ville, profession: profession ?? this.profession, nationalite: nationalite ?? this.nationalite, membreBureau: membreBureau ?? this.membreBureau, responsable: responsable ?? this.responsable, includeInactifs: includeInactifs ?? this.includeInactifs, ); } /// Critères vides static const empty = MembreSearchCriteria(); /// Critères pour recherche rapide par nom/prénom static MembreSearchCriteria quickSearch(String query) { return MembreSearchCriteria(query: query); } /// Critères pour membres actifs uniquement static const activeMembers = MembreSearchCriteria( statut: 'ACTIF', includeInactifs: false, ); /// Critères pour membres du bureau static const bureauMembers = MembreSearchCriteria( membreBureau: true, statut: 'ACTIF', ); @override bool operator ==(Object other) => identical(this, other) || other is MembreSearchCriteria && runtimeType == other.runtimeType && query == other.query && nom == other.nom && prenom == other.prenom && email == other.email && telephone == other.telephone && organisationIds == other.organisationIds && roles == other.roles && statut == other.statut && dateAdhesionMin == other.dateAdhesionMin && dateAdhesionMax == other.dateAdhesionMax && ageMin == other.ageMin && ageMax == other.ageMax && region == other.region && ville == other.ville && profession == other.profession && nationalite == other.nationalite && membreBureau == other.membreBureau && responsable == other.responsable && includeInactifs == other.includeInactifs; @override int get hashCode => Object.hashAll([ query, nom, prenom, email, telephone, organisationIds, roles, statut, dateAdhesionMin, dateAdhesionMax, ageMin, ageMax, region, ville, profession, nationalite, membreBureau, responsable, includeInactifs, ]); @override String toString() => 'MembreSearchCriteria(${description.isNotEmpty ? description : 'empty'})'; }