import 'membre_search_criteria.dart'; import '../../features/members/data/models/membre_complete_model.dart'; /// Modèle pour les résultats de recherche avancée des membres /// Correspond au DTO Java MembreSearchResultDTO class MembreSearchResult { /// Liste des membres trouvés final List membres; /// Nombre total de résultats (toutes pages confondues) final int totalElements; /// Nombre total de pages final int totalPages; /// Numéro de la page actuelle (0-based) final int currentPage; /// Taille de la page final int pageSize; /// Nombre d'éléments sur la page actuelle final int numberOfElements; /// Indique s'il y a une page suivante final bool hasNext; /// Indique s'il y a une page précédente final bool hasPrevious; /// Indique si c'est la première page final bool isFirst; /// Indique si c'est la dernière page final bool isLast; /// Critères de recherche utilisés final MembreSearchCriteria criteria; /// Temps d'exécution de la recherche en millisecondes final int executionTimeMs; /// Statistiques de recherche final SearchStatistics? statistics; const MembreSearchResult({ required this.membres, required this.totalElements, required this.totalPages, required this.currentPage, required this.pageSize, required this.numberOfElements, required this.hasNext, required this.hasPrevious, required this.isFirst, required this.isLast, required this.criteria, required this.executionTimeMs, this.statistics, }); /// Factory constructor pour créer depuis JSON factory MembreSearchResult.fromJson(Map json) { return MembreSearchResult( membres: (json['membres'] as List?) ?.map((e) => MembreCompletModel.fromJson(e as Map)) .toList() ?? [], totalElements: json['totalElements'] as int? ?? 0, totalPages: json['totalPages'] as int? ?? 0, currentPage: json['currentPage'] as int? ?? 0, pageSize: json['pageSize'] as int? ?? 20, numberOfElements: json['numberOfElements'] as int? ?? 0, hasNext: json['hasNext'] as bool? ?? false, hasPrevious: json['hasPrevious'] as bool? ?? false, isFirst: json['isFirst'] as bool? ?? true, isLast: json['isLast'] as bool? ?? true, criteria: MembreSearchCriteria.fromJson(json['criteria'] as Map? ?? {}), executionTimeMs: json['executionTimeMs'] as int? ?? 0, statistics: json['statistics'] != null ? SearchStatistics.fromJson(json['statistics'] as Map) : null, ); } /// Convertit vers JSON Map toJson() { return { 'membres': membres.map((e) => e.toJson()).toList(), 'totalElements': totalElements, 'totalPages': totalPages, 'currentPage': currentPage, 'pageSize': pageSize, 'numberOfElements': numberOfElements, 'hasNext': hasNext, 'hasPrevious': hasPrevious, 'isFirst': isFirst, 'isLast': isLast, 'criteria': criteria.toJson(), 'executionTimeMs': executionTimeMs, 'statistics': statistics?.toJson(), }; } /// Vérifie si les résultats sont vides bool get isEmpty => membres.isEmpty; /// Vérifie si les résultats ne sont pas vides bool get isNotEmpty => membres.isNotEmpty; /// Retourne le numéro de la page suivante (1-based pour affichage) int get nextPageNumber => hasNext ? currentPage + 2 : -1; /// Retourne le numéro de la page précédente (1-based pour affichage) int get previousPageNumber => hasPrevious ? currentPage : -1; /// Retourne une description textuelle des résultats String get resultDescription { if (isEmpty) { return 'Aucun membre trouvé'; } if (totalElements == 1) { return '1 membre trouvé'; } if (totalPages == 1) { return '$totalElements membres trouvés'; } final startElement = currentPage * pageSize + 1; final endElement = (startElement + numberOfElements - 1).clamp(1, totalElements); return 'Membres $startElement-$endElement sur $totalElements (page ${currentPage + 1}/$totalPages)'; } /// Résultat vide static MembreSearchResult empty(MembreSearchCriteria criteria) { return MembreSearchResult( membres: const [], totalElements: 0, totalPages: 0, currentPage: 0, pageSize: 20, numberOfElements: 0, hasNext: false, hasPrevious: false, isFirst: true, isLast: true, criteria: criteria, executionTimeMs: 0, ); } @override String toString() => 'MembreSearchResult($resultDescription, ${executionTimeMs}ms)'; } /// Statistiques sur les résultats de recherche class SearchStatistics { /// Nombre de membres actifs dans les résultats final int membresActifs; /// Nombre de membres inactifs dans les résultats final int membresInactifs; /// Âge moyen des membres trouvés final double ageMoyen; /// Âge minimum des membres trouvés final int ageMin; /// Âge maximum des membres trouvés final int ageMax; /// Nombre d'organisations représentées final int nombreOrganisations; /// Nombre de régions représentées final int nombreRegions; /// Ancienneté moyenne en années final double ancienneteMoyenne; const SearchStatistics({ required this.membresActifs, required this.membresInactifs, required this.ageMoyen, required this.ageMin, required this.ageMax, required this.nombreOrganisations, required this.nombreRegions, required this.ancienneteMoyenne, }); /// Factory constructor pour créer depuis JSON factory SearchStatistics.fromJson(Map json) { return SearchStatistics( membresActifs: json['membresActifs'] as int? ?? 0, membresInactifs: json['membresInactifs'] as int? ?? 0, ageMoyen: (json['ageMoyen'] as num?)?.toDouble() ?? 0.0, ageMin: json['ageMin'] as int? ?? 0, ageMax: json['ageMax'] as int? ?? 0, nombreOrganisations: json['nombreOrganisations'] as int? ?? 0, nombreRegions: json['nombreRegions'] as int? ?? 0, ancienneteMoyenne: (json['ancienneteMoyenne'] as num?)?.toDouble() ?? 0.0, ); } /// Convertit vers JSON Map toJson() { return { 'membresActifs': membresActifs, 'membresInactifs': membresInactifs, 'ageMoyen': ageMoyen, 'ageMin': ageMin, 'ageMax': ageMax, 'nombreOrganisations': nombreOrganisations, 'nombreRegions': nombreRegions, 'ancienneteMoyenne': ancienneteMoyenne, }; } /// Nombre total de membres int get totalMembres => membresActifs + membresInactifs; /// Pourcentage de membres actifs double get pourcentageActifs { if (totalMembres == 0) return 0.0; return (membresActifs / totalMembres) * 100; } /// Pourcentage de membres inactifs double get pourcentageInactifs { if (totalMembres == 0) return 0.0; return (membresInactifs / totalMembres) * 100; } /// Tranche d'âge String get trancheAge { if (ageMin == ageMax) return '$ageMin ans'; return '$ageMin-$ageMax ans'; } /// Description textuelle des statistiques String get description { final parts = []; if (totalMembres > 0) { parts.add('$totalMembres membres'); if (membresActifs > 0) { parts.add('${pourcentageActifs.toStringAsFixed(1)}% actifs'); } if (ageMoyen > 0) { parts.add('âge moyen: ${ageMoyen.toStringAsFixed(1)} ans'); } if (nombreOrganisations > 0) { parts.add('$nombreOrganisations organisations'); } if (ancienneteMoyenne > 0) { parts.add('ancienneté: ${ancienneteMoyenne.toStringAsFixed(1)} ans'); } } return parts.join(' • '); } @override String toString() => 'SearchStatistics($description)'; }