Clean project: remove test files, debug logs, and add documentation

This commit is contained in:
dahoud
2025-10-05 13:41:33 +00:00
parent 96a17eadbd
commit 291847924c
438 changed files with 65754 additions and 32713 deletions

View File

@@ -0,0 +1,348 @@
/// Modèle complet de données pour un événement
/// Aligné avec le backend EvenementDTO
library evenement_model;
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'evenement_model.g.dart';
/// Énumération des types d'événements
enum TypeEvenement {
@JsonValue('ASSEMBLEE_GENERALE')
assembleeGenerale,
@JsonValue('REUNION')
reunion,
@JsonValue('FORMATION')
formation,
@JsonValue('CONFERENCE')
conference,
@JsonValue('ATELIER')
atelier,
@JsonValue('SEMINAIRE')
seminaire,
@JsonValue('EVENEMENT_SOCIAL')
evenementSocial,
@JsonValue('MANIFESTATION')
manifestation,
@JsonValue('CELEBRATION')
celebration,
@JsonValue('AUTRE')
autre,
}
/// Énumération des statuts d'événements
enum StatutEvenement {
@JsonValue('PLANIFIE')
planifie,
@JsonValue('CONFIRME')
confirme,
@JsonValue('EN_COURS')
enCours,
@JsonValue('TERMINE')
termine,
@JsonValue('ANNULE')
annule,
@JsonValue('REPORTE')
reporte,
}
/// Énumération des priorités
enum PrioriteEvenement {
@JsonValue('BASSE')
basse,
@JsonValue('MOYENNE')
moyenne,
@JsonValue('HAUTE')
haute,
}
/// Modèle complet d'un événement
@JsonSerializable()
class EvenementModel extends Equatable {
/// Identifiant unique
final String? id;
/// Titre de l'événement
final String titre;
/// Description détaillée
final String? description;
/// Date et heure de début
@JsonKey(name: 'dateDebut')
final DateTime dateDebut;
/// Date et heure de fin
@JsonKey(name: 'dateFin')
final DateTime dateFin;
/// Lieu de l'événement
final String? lieu;
/// Adresse complète
final String? adresse;
/// Ville
final String? ville;
/// Code postal
@JsonKey(name: 'codePostal')
final String? codePostal;
/// Type d'événement
final TypeEvenement type;
/// Statut de l'événement
final StatutEvenement statut;
/// Nombre maximum de participants
@JsonKey(name: 'maxParticipants')
final int? maxParticipants;
/// Nombre de participants actuels
@JsonKey(name: 'participantsActuels')
final int participantsActuels;
/// ID de l'organisateur
@JsonKey(name: 'organisateurId')
final String? organisateurId;
/// Nom de l'organisateur (pour affichage)
@JsonKey(name: 'organisateurNom')
final String? organisateurNom;
/// ID de l'organisation
@JsonKey(name: 'organisationId')
final String? organisationId;
/// Nom de l'organisation (pour affichage)
@JsonKey(name: 'organisationNom')
final String? organisationNom;
/// Priorité de l'événement
final PrioriteEvenement priorite;
/// Événement public
@JsonKey(name: 'estPublic')
final bool estPublic;
/// Inscription requise
@JsonKey(name: 'inscriptionRequise')
final bool inscriptionRequise;
/// Coût de participation
final double? cout;
/// Devise
final String devise;
/// Tags/mots-clés
final List<String> tags;
/// URL de l'image
@JsonKey(name: 'imageUrl')
final String? imageUrl;
/// URL du document
@JsonKey(name: 'documentUrl')
final String? documentUrl;
/// Notes internes
final String? notes;
/// Date de création
@JsonKey(name: 'dateCreation')
final DateTime? dateCreation;
/// Date de modification
@JsonKey(name: 'dateModification')
final DateTime? dateModification;
/// Actif
final bool actif;
const EvenementModel({
this.id,
required this.titre,
this.description,
required this.dateDebut,
required this.dateFin,
this.lieu,
this.adresse,
this.ville,
this.codePostal,
this.type = TypeEvenement.autre,
this.statut = StatutEvenement.planifie,
this.maxParticipants,
this.participantsActuels = 0,
this.organisateurId,
this.organisateurNom,
this.organisationId,
this.organisationNom,
this.priorite = PrioriteEvenement.moyenne,
this.estPublic = true,
this.inscriptionRequise = false,
this.cout,
this.devise = 'XOF',
this.tags = const [],
this.imageUrl,
this.documentUrl,
this.notes,
this.dateCreation,
this.dateModification,
this.actif = true,
});
/// Création depuis JSON
factory EvenementModel.fromJson(Map<String, dynamic> json) =>
_$EvenementModelFromJson(json);
/// Conversion vers JSON
Map<String, dynamic> toJson() => _$EvenementModelToJson(this);
/// Copie avec modifications
EvenementModel copyWith({
String? id,
String? titre,
String? description,
DateTime? dateDebut,
DateTime? dateFin,
String? lieu,
String? adresse,
String? ville,
String? codePostal,
TypeEvenement? type,
StatutEvenement? statut,
int? maxParticipants,
int? participantsActuels,
String? organisateurId,
String? organisateurNom,
String? organisationId,
String? organisationNom,
PrioriteEvenement? priorite,
bool? estPublic,
bool? inscriptionRequise,
double? cout,
String? devise,
List<String>? tags,
String? imageUrl,
String? documentUrl,
String? notes,
DateTime? dateCreation,
DateTime? dateModification,
bool? actif,
}) {
return EvenementModel(
id: id ?? this.id,
titre: titre ?? this.titre,
description: description ?? this.description,
dateDebut: dateDebut ?? this.dateDebut,
dateFin: dateFin ?? this.dateFin,
lieu: lieu ?? this.lieu,
adresse: adresse ?? this.adresse,
ville: ville ?? this.ville,
codePostal: codePostal ?? this.codePostal,
type: type ?? this.type,
statut: statut ?? this.statut,
maxParticipants: maxParticipants ?? this.maxParticipants,
participantsActuels: participantsActuels ?? this.participantsActuels,
organisateurId: organisateurId ?? this.organisateurId,
organisateurNom: organisateurNom ?? this.organisateurNom,
organisationId: organisationId ?? this.organisationId,
organisationNom: organisationNom ?? this.organisationNom,
priorite: priorite ?? this.priorite,
estPublic: estPublic ?? this.estPublic,
inscriptionRequise: inscriptionRequise ?? this.inscriptionRequise,
cout: cout ?? this.cout,
devise: devise ?? this.devise,
tags: tags ?? this.tags,
imageUrl: imageUrl ?? this.imageUrl,
documentUrl: documentUrl ?? this.documentUrl,
notes: notes ?? this.notes,
dateCreation: dateCreation ?? this.dateCreation,
dateModification: dateModification ?? this.dateModification,
actif: actif ?? this.actif,
);
}
/// Durée de l'événement en heures
double get dureeHeures {
return dateFin.difference(dateDebut).inMinutes / 60.0;
}
/// Nombre de jours avant l'événement
int get joursAvantEvenement {
return dateDebut.difference(DateTime.now()).inDays;
}
/// Est dans le futur
bool get estAVenir => dateDebut.isAfter(DateTime.now());
/// Est en cours
bool get estEnCours {
final now = DateTime.now();
return now.isAfter(dateDebut) && now.isBefore(dateFin);
}
/// Est passé
bool get estPasse => dateFin.isBefore(DateTime.now());
/// Places disponibles
int? get placesDisponibles {
if (maxParticipants == null) return null;
return maxParticipants! - participantsActuels;
}
/// Est complet
bool get estComplet {
if (maxParticipants == null) return false;
return participantsActuels >= maxParticipants!;
}
/// Peut s'inscrire
bool get peutSinscrire {
return estAVenir &&
!estComplet &&
statut == StatutEvenement.confirme &&
inscriptionRequise;
}
@override
List<Object?> get props => [
id,
titre,
description,
dateDebut,
dateFin,
lieu,
adresse,
ville,
codePostal,
type,
statut,
maxParticipants,
participantsActuels,
organisateurId,
organisateurNom,
organisationId,
organisationNom,
priorite,
estPublic,
inscriptionRequise,
cout,
devise,
tags,
imageUrl,
documentUrl,
notes,
dateCreation,
dateModification,
actif,
];
@override
String toString() =>
'EvenementModel(id: $id, titre: $titre, dateDebut: $dateDebut, statut: $statut)';
}

View File

@@ -0,0 +1,111 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'evenement_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
EvenementModel _$EvenementModelFromJson(Map<String, dynamic> json) =>
EvenementModel(
id: json['id'] as String?,
titre: json['titre'] as String,
description: json['description'] as String?,
dateDebut: DateTime.parse(json['dateDebut'] as String),
dateFin: DateTime.parse(json['dateFin'] as String),
lieu: json['lieu'] as String?,
adresse: json['adresse'] as String?,
ville: json['ville'] as String?,
codePostal: json['codePostal'] as String?,
type: $enumDecodeNullable(_$TypeEvenementEnumMap, json['type']) ??
TypeEvenement.autre,
statut: $enumDecodeNullable(_$StatutEvenementEnumMap, json['statut']) ??
StatutEvenement.planifie,
maxParticipants: (json['maxParticipants'] as num?)?.toInt(),
participantsActuels: (json['participantsActuels'] as num?)?.toInt() ?? 0,
organisateurId: json['organisateurId'] as String?,
organisateurNom: json['organisateurNom'] as String?,
organisationId: json['organisationId'] as String?,
organisationNom: json['organisationNom'] as String?,
priorite:
$enumDecodeNullable(_$PrioriteEvenementEnumMap, json['priorite']) ??
PrioriteEvenement.moyenne,
estPublic: json['estPublic'] as bool? ?? true,
inscriptionRequise: json['inscriptionRequise'] as bool? ?? false,
cout: (json['cout'] as num?)?.toDouble(),
devise: json['devise'] as String? ?? 'XOF',
tags:
(json['tags'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const [],
imageUrl: json['imageUrl'] as String?,
documentUrl: json['documentUrl'] as String?,
notes: json['notes'] as String?,
dateCreation: json['dateCreation'] == null
? null
: DateTime.parse(json['dateCreation'] as String),
dateModification: json['dateModification'] == null
? null
: DateTime.parse(json['dateModification'] as String),
actif: json['actif'] as bool? ?? true,
);
Map<String, dynamic> _$EvenementModelToJson(EvenementModel instance) =>
<String, dynamic>{
'id': instance.id,
'titre': instance.titre,
'description': instance.description,
'dateDebut': instance.dateDebut.toIso8601String(),
'dateFin': instance.dateFin.toIso8601String(),
'lieu': instance.lieu,
'adresse': instance.adresse,
'ville': instance.ville,
'codePostal': instance.codePostal,
'type': _$TypeEvenementEnumMap[instance.type]!,
'statut': _$StatutEvenementEnumMap[instance.statut]!,
'maxParticipants': instance.maxParticipants,
'participantsActuels': instance.participantsActuels,
'organisateurId': instance.organisateurId,
'organisateurNom': instance.organisateurNom,
'organisationId': instance.organisationId,
'organisationNom': instance.organisationNom,
'priorite': _$PrioriteEvenementEnumMap[instance.priorite]!,
'estPublic': instance.estPublic,
'inscriptionRequise': instance.inscriptionRequise,
'cout': instance.cout,
'devise': instance.devise,
'tags': instance.tags,
'imageUrl': instance.imageUrl,
'documentUrl': instance.documentUrl,
'notes': instance.notes,
'dateCreation': instance.dateCreation?.toIso8601String(),
'dateModification': instance.dateModification?.toIso8601String(),
'actif': instance.actif,
};
const _$TypeEvenementEnumMap = {
TypeEvenement.assembleeGenerale: 'ASSEMBLEE_GENERALE',
TypeEvenement.reunion: 'REUNION',
TypeEvenement.formation: 'FORMATION',
TypeEvenement.conference: 'CONFERENCE',
TypeEvenement.atelier: 'ATELIER',
TypeEvenement.seminaire: 'SEMINAIRE',
TypeEvenement.evenementSocial: 'EVENEMENT_SOCIAL',
TypeEvenement.manifestation: 'MANIFESTATION',
TypeEvenement.celebration: 'CELEBRATION',
TypeEvenement.autre: 'AUTRE',
};
const _$StatutEvenementEnumMap = {
StatutEvenement.planifie: 'PLANIFIE',
StatutEvenement.confirme: 'CONFIRME',
StatutEvenement.enCours: 'EN_COURS',
StatutEvenement.termine: 'TERMINE',
StatutEvenement.annule: 'ANNULE',
StatutEvenement.reporte: 'REPORTE',
};
const _$PrioriteEvenementEnumMap = {
PrioriteEvenement.basse: 'BASSE',
PrioriteEvenement.moyenne: 'MOYENNE',
PrioriteEvenement.haute: 'HAUTE',
};

View File

@@ -0,0 +1,358 @@
/// Repository pour la gestion des événements
/// Interface avec l'API backend EvenementResource
library evenement_repository;
import 'package:dio/dio.dart';
import '../models/evenement_model.dart';
/// Résultat de recherche paginé
class EvenementSearchResult {
final List<EvenementModel> evenements;
final int total;
final int page;
final int size;
final int totalPages;
const EvenementSearchResult({
required this.evenements,
required this.total,
required this.page,
required this.size,
required this.totalPages,
});
factory EvenementSearchResult.fromJson(Map<String, dynamic> json) {
// Support pour les deux formats de réponse
if (json.containsKey('data')) {
// Format paginé avec métadonnées
return EvenementSearchResult(
evenements: (json['data'] as List<dynamic>)
.map((e) => EvenementModel.fromJson(e as Map<String, dynamic>))
.toList(),
total: json['total'] as int,
page: json['page'] as int,
size: json['size'] as int,
totalPages: json['totalPages'] as int,
);
} else {
// Format simple (liste directe) - pour compatibilité backend
return EvenementSearchResult(
evenements: (json['content'] as List<dynamic>)
.map((e) => EvenementModel.fromJson(e as Map<String, dynamic>))
.toList(),
total: json['totalElements'] as int? ?? 0,
page: json['number'] as int? ?? 0,
size: json['size'] as int? ?? 20,
totalPages: json['totalPages'] as int? ?? 1,
);
}
}
}
/// Interface du repository des événements
abstract class EvenementRepository {
/// Récupère la liste des événements avec pagination
Future<EvenementSearchResult> getEvenements({
int page = 0,
int size = 20,
String? recherche,
});
/// Récupère un événement par son ID
Future<EvenementModel?> getEvenementById(String id);
/// Crée un nouvel événement
Future<EvenementModel> createEvenement(EvenementModel evenement);
/// Met à jour un événement
Future<EvenementModel> updateEvenement(String id, EvenementModel evenement);
/// Supprime un événement
Future<void> deleteEvenement(String id);
/// Récupère les événements à venir
Future<EvenementSearchResult> getEvenementsAVenir({int page = 0, int size = 20});
/// Récupère les événements en cours
Future<EvenementSearchResult> getEvenementsEnCours({int page = 0, int size = 20});
/// Récupère les événements passés
Future<EvenementSearchResult> getEvenementsPasses({int page = 0, int size = 20});
/// S'inscrire à un événement
Future<void> inscrireEvenement(String evenementId);
/// Se désinscrire d'un événement
Future<void> desinscrireEvenement(String evenementId);
/// Récupère les participants d'un événement
Future<List<Map<String, dynamic>>> getParticipants(String evenementId);
/// Récupère les statistiques des événements
Future<Map<String, dynamic>> getEvenementsStats();
}
/// Implémentation du repository des événements
class EvenementRepositoryImpl implements EvenementRepository {
final Dio _dio;
static const String _baseUrl = '/api/evenements';
EvenementRepositoryImpl(this._dio);
@override
Future<EvenementSearchResult> getEvenements({
int page = 0,
int size = 20,
String? recherche,
}) async {
try {
final queryParams = <String, dynamic>{
'page': page,
'size': size,
};
if (recherche?.isNotEmpty == true) {
queryParams['recherche'] = recherche;
}
final response = await _dio.get(
_baseUrl,
queryParameters: queryParams,
);
if (response.statusCode == 200) {
// Le backend peut retourner soit une liste directe, soit un objet paginé
if (response.data is List) {
// Format liste directe
final evenements = (response.data as List<dynamic>)
.map((e) => EvenementModel.fromJson(e as Map<String, dynamic>))
.toList();
return EvenementSearchResult(
evenements: evenements,
total: evenements.length,
page: page,
size: size,
totalPages: 1,
);
} else {
// Format objet paginé
return EvenementSearchResult.fromJson(response.data as Map<String, dynamic>);
}
} else {
throw Exception('Erreur lors de la récupération des événements: ${response.statusCode}');
}
} on DioException catch (e) {
if (e.response != null) {
throw Exception('Erreur HTTP ${e.response!.statusCode}: ${e.response!.data}');
} else {
throw Exception('Erreur réseau: ${e.type} - ${e.message ?? e.error}');
}
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des événements: $e');
}
}
@override
Future<EvenementModel?> getEvenementById(String id) async {
try {
final response = await _dio.get('$_baseUrl/$id');
if (response.statusCode == 200) {
return EvenementModel.fromJson(response.data as Map<String, dynamic>);
} else if (response.statusCode == 404) {
return null;
} else {
throw Exception('Erreur lors de la récupération de l\'événement: ${response.statusCode}');
}
} on DioException catch (e) {
if (e.response?.statusCode == 404) {
return null;
}
throw Exception('Erreur réseau lors de la récupération de l\'événement: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération de l\'événement: $e');
}
}
@override
Future<EvenementModel> createEvenement(EvenementModel evenement) async {
try {
final response = await _dio.post(
_baseUrl,
data: evenement.toJson(),
);
if (response.statusCode == 201 || response.statusCode == 200) {
return EvenementModel.fromJson(response.data as Map<String, dynamic>);
} else {
throw Exception('Erreur lors de la création de l\'événement: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la création de l\'événement: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la création de l\'événement: $e');
}
}
@override
Future<EvenementModel> updateEvenement(String id, EvenementModel evenement) async {
try {
final response = await _dio.put(
'$_baseUrl/$id',
data: evenement.toJson(),
);
if (response.statusCode == 200) {
return EvenementModel.fromJson(response.data as Map<String, dynamic>);
} else {
throw Exception('Erreur lors de la mise à jour de l\'événement: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la mise à jour de l\'événement: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la mise à jour de l\'événement: $e');
}
}
@override
Future<void> deleteEvenement(String id) async {
try {
final response = await _dio.delete('$_baseUrl/$id');
if (response.statusCode != 204 && response.statusCode != 200) {
throw Exception('Erreur lors de la suppression de l\'événement: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la suppression de l\'événement: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la suppression de l\'événement: $e');
}
}
@override
Future<EvenementSearchResult> getEvenementsAVenir({int page = 0, int size = 20}) async {
try {
final response = await _dio.get(
'$_baseUrl/a-venir',
queryParameters: {'page': page, 'size': size},
);
if (response.statusCode == 200) {
return EvenementSearchResult.fromJson(response.data as Map<String, dynamic>);
} else {
throw Exception('Erreur lors de la récupération des événements à venir: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la récupération des événements à venir: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des événements à venir: $e');
}
}
@override
Future<EvenementSearchResult> getEvenementsEnCours({int page = 0, int size = 20}) async {
try {
final response = await _dio.get(
'$_baseUrl/en-cours',
queryParameters: {'page': page, 'size': size},
);
if (response.statusCode == 200) {
return EvenementSearchResult.fromJson(response.data as Map<String, dynamic>);
} else {
throw Exception('Erreur lors de la récupération des événements en cours: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la récupération des événements en cours: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des événements en cours: $e');
}
}
@override
Future<EvenementSearchResult> getEvenementsPasses({int page = 0, int size = 20}) async {
try {
final response = await _dio.get(
'$_baseUrl/passes',
queryParameters: {'page': page, 'size': size},
);
if (response.statusCode == 200) {
return EvenementSearchResult.fromJson(response.data as Map<String, dynamic>);
} else {
throw Exception('Erreur lors de la récupération des événements passés: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la récupération des événements passés: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des événements passés: $e');
}
}
@override
Future<void> inscrireEvenement(String evenementId) async {
try {
final response = await _dio.post('$_baseUrl/$evenementId/inscrire');
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception('Erreur lors de l\'inscription à l\'événement: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de l\'inscription à l\'événement: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de l\'inscription à l\'événement: $e');
}
}
@override
Future<void> desinscrireEvenement(String evenementId) async {
try {
final response = await _dio.delete('$_baseUrl/$evenementId/desinscrire');
if (response.statusCode != 200 && response.statusCode != 204) {
throw Exception('Erreur lors de la désinscription de l\'événement: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la désinscription de l\'événement: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la désinscription de l\'événement: $e');
}
}
@override
Future<List<Map<String, dynamic>>> getParticipants(String evenementId) async {
try {
final response = await _dio.get('$_baseUrl/$evenementId/participants');
if (response.statusCode == 200) {
return (response.data as List<dynamic>)
.map((e) => e as Map<String, dynamic>)
.toList();
} else {
throw Exception('Erreur lors de la récupération des participants: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la récupération des participants: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des participants: $e');
}
}
@override
Future<Map<String, dynamic>> getEvenementsStats() async {
try {
final response = await _dio.get('$_baseUrl/statistiques');
if (response.statusCode == 200) {
return response.data as Map<String, dynamic>;
} else {
throw Exception('Erreur lors de la récupération des statistiques: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la récupération des statistiques: ${e.message}');
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des statistiques: $e');
}
}
}