Files
unionflow-client-quarkus-pr…/unionflow-mobile-apps/lib/shared/models/membre_search_result.dart
2025-11-17 16:02:04 +00:00

270 lines
7.8 KiB
Dart

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<MembreCompletModel> 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<String, dynamic> json) {
return MembreSearchResult(
membres: (json['membres'] as List<dynamic>?)
?.map((e) => MembreCompletModel.fromJson(e as Map<String, dynamic>))
.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<String, dynamic>? ?? {}),
executionTimeMs: json['executionTimeMs'] as int? ?? 0,
statistics: json['statistics'] != null
? SearchStatistics.fromJson(json['statistics'] as Map<String, dynamic>)
: null,
);
}
/// Convertit vers JSON
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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 = <String>[];
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)';
}