Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts). Signed-off-by: lions dev Team
This commit is contained in:
108
lib/features/reports/data/models/analytics_model.dart
Normal file
108
lib/features/reports/data/models/analytics_model.dart
Normal file
@@ -0,0 +1,108 @@
|
||||
library analytics_model;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Modèle pour une métrique analytics — aligné avec AnalyticsDataDTO
|
||||
class AnalyticsModel extends Equatable {
|
||||
final String? id;
|
||||
final String typeMetrique;
|
||||
final String periodeAnalyse;
|
||||
final double valeur;
|
||||
final double? valeurPrecedente;
|
||||
final double? pourcentageEvolution;
|
||||
final DateTime? dateDebut;
|
||||
final DateTime? dateFin;
|
||||
final String? libelle;
|
||||
final String? description;
|
||||
final String? unite;
|
||||
final String? couleur;
|
||||
final String? icone;
|
||||
|
||||
const AnalyticsModel({
|
||||
this.id,
|
||||
required this.typeMetrique,
|
||||
required this.periodeAnalyse,
|
||||
required this.valeur,
|
||||
this.valeurPrecedente,
|
||||
this.pourcentageEvolution,
|
||||
this.dateDebut,
|
||||
this.dateFin,
|
||||
this.libelle,
|
||||
this.description,
|
||||
this.unite,
|
||||
this.couleur,
|
||||
this.icone,
|
||||
});
|
||||
|
||||
bool get hasPositiveTrend =>
|
||||
pourcentageEvolution != null && pourcentageEvolution! > 0;
|
||||
bool get hasNegativeTrend =>
|
||||
pourcentageEvolution != null && pourcentageEvolution! < 0;
|
||||
|
||||
factory AnalyticsModel.fromJson(Map<String, dynamic> json) {
|
||||
return AnalyticsModel(
|
||||
id: json['id']?.toString(),
|
||||
typeMetrique: json['typeMetrique']?.toString() ?? '',
|
||||
periodeAnalyse: json['periodeAnalyse']?.toString() ?? '',
|
||||
valeur: _parseDouble(json['valeur']),
|
||||
valeurPrecedente: json['valeurPrecedente'] != null
|
||||
? _parseDouble(json['valeurPrecedente'])
|
||||
: null,
|
||||
pourcentageEvolution: json['pourcentageEvolution'] != null
|
||||
? _parseDouble(json['pourcentageEvolution'])
|
||||
: null,
|
||||
dateDebut: json['dateDebut'] != null
|
||||
? DateTime.tryParse(json['dateDebut'].toString())
|
||||
: null,
|
||||
dateFin: json['dateFin'] != null
|
||||
? DateTime.tryParse(json['dateFin'].toString())
|
||||
: null,
|
||||
libelle: json['libellePersonnalise']?.toString() ?? json['typeMetrique']?.toString(),
|
||||
description: json['description']?.toString(),
|
||||
unite: json['unite']?.toString(),
|
||||
couleur: json['couleur']?.toString(),
|
||||
icone: json['icone']?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
static double _parseDouble(dynamic val) {
|
||||
if (val == null) return 0.0;
|
||||
if (val is num) return val.toDouble();
|
||||
return double.tryParse(val.toString()) ?? 0.0;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, typeMetrique, valeur, periodeAnalyse];
|
||||
}
|
||||
|
||||
/// KPI synthétique pour le tableau de bord des rapports
|
||||
class KpiModel extends Equatable {
|
||||
final String libelle;
|
||||
final double valeur;
|
||||
final String? unite;
|
||||
final double? evolution;
|
||||
final String? couleur;
|
||||
|
||||
const KpiModel({
|
||||
required this.libelle,
|
||||
required this.valeur,
|
||||
this.unite,
|
||||
this.evolution,
|
||||
this.couleur,
|
||||
});
|
||||
|
||||
factory KpiModel.fromJson(Map<String, dynamic> json) {
|
||||
return KpiModel(
|
||||
libelle: json['libelle']?.toString() ?? json['nom']?.toString() ?? '',
|
||||
valeur: AnalyticsModel._parseDouble(json['valeur'] ?? json['value']),
|
||||
unite: json['unite']?.toString(),
|
||||
evolution: json['evolution'] != null
|
||||
? AnalyticsModel._parseDouble(json['evolution'])
|
||||
: null,
|
||||
couleur: json['couleur']?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [libelle, valeur];
|
||||
}
|
||||
230
lib/features/reports/data/repositories/reports_repository.dart
Normal file
230
lib/features/reports/data/repositories/reports_repository.dart
Normal file
@@ -0,0 +1,230 @@
|
||||
/// Repository pour la gestion des rapports et analytics
|
||||
library reports_repository_impl;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:unionflow_mobile_apps/core/network/api_client.dart';
|
||||
import 'package:unionflow_mobile_apps/core/utils/logger.dart';
|
||||
import '../../domain/repositories/reports_repository.dart';
|
||||
import '../models/analytics_model.dart';
|
||||
|
||||
/// Implémentation du repository des rapports
|
||||
@LazySingleton(as: IReportsRepository)
|
||||
class ReportsRepositoryImpl implements IReportsRepository {
|
||||
final ApiClient _apiClient;
|
||||
static const String _analyticsBase = '/api/v1/analytics';
|
||||
static const String _membresBase = '/api/membres';
|
||||
static const String _cotisationsBase = '/api/cotisations';
|
||||
static const String _evenementsBase = '/api/evenements';
|
||||
|
||||
ReportsRepositoryImpl(this._apiClient);
|
||||
|
||||
@override
|
||||
Future<List<AnalyticsModel>> getMetriques(String typeMetrique, String periode) async {
|
||||
try {
|
||||
final response = await _apiClient.get(
|
||||
'$_analyticsBase/metriques/$typeMetrique',
|
||||
queryParameters: {'periodeAnalyse': periode},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
if (data is List) {
|
||||
return data.map((e) => AnalyticsModel.fromJson(e as Map<String, dynamic>)).toList();
|
||||
}
|
||||
if (data is Map) {
|
||||
return [AnalyticsModel.fromJson(data as Map<String, dynamic>)];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getMetriques échoué', error: e, stackTrace: st);
|
||||
if (e.response?.statusCode == 404 || e.response?.statusCode == 400) return [];
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> getPerformanceGlobale() async {
|
||||
try {
|
||||
final response = await _apiClient.get('$_analyticsBase/performance-globale');
|
||||
if (response.statusCode == 200 && response.data is Map) {
|
||||
return response.data as Map<String, dynamic>;
|
||||
}
|
||||
return {};
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getPerformanceGlobale échoué', error: e, stackTrace: st);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AnalyticsModel>> getEvolutions(String typeMetrique) async {
|
||||
try {
|
||||
final response = await _apiClient.get(
|
||||
'$_analyticsBase/evolutions',
|
||||
queryParameters: {'typeMetrique': typeMetrique},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
if (data is List) {
|
||||
return data.map((e) => AnalyticsModel.fromJson(e as Map<String, dynamic>)).toList();
|
||||
}
|
||||
}
|
||||
return [];
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getEvolutions échoué', error: e, stackTrace: st);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> getStatistiquesMembres() async {
|
||||
try {
|
||||
final response = await _apiClient.get('$_membresBase/statistiques');
|
||||
if (response.statusCode == 200 && response.data is Map) {
|
||||
return response.data as Map<String, dynamic>;
|
||||
}
|
||||
return {};
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getStatistiquesMembres échoué', error: e, stackTrace: st);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> getStatistiquesCotisations(int annee) async {
|
||||
try {
|
||||
final response = await _apiClient.get(
|
||||
'$_cotisationsBase/statistiques',
|
||||
queryParameters: {'annee': annee},
|
||||
);
|
||||
if (response.statusCode == 200 && response.data is Map) {
|
||||
return response.data as Map<String, dynamic>;
|
||||
}
|
||||
return {};
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getStatistiquesCotisations échoué', error: e, stackTrace: st);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> getStatistiquesEvenements() async {
|
||||
try {
|
||||
final response = await _apiClient.get('$_evenementsBase/statistiques');
|
||||
if (response.statusCode == 200 && response.data is Map) {
|
||||
return response.data as Map<String, dynamic>;
|
||||
}
|
||||
return {};
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getStatistiquesEvenements échoué', error: e, stackTrace: st);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> getAvailableReports() async {
|
||||
try {
|
||||
final response = await _apiClient.get('$_analyticsBase/reports/available');
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
if (data is List) {
|
||||
return data.map((e) => Map<String, dynamic>.from(e as Map)).toList();
|
||||
}
|
||||
}
|
||||
return [];
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getAvailableReports échoué', error: e, stackTrace: st);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> generateReport(String type, {String? format}) async {
|
||||
try {
|
||||
final queryParams = <String, dynamic>{'type': type};
|
||||
if (format != null) queryParams['format'] = format;
|
||||
final response = await _apiClient.post(
|
||||
'$_analyticsBase/reports/generate',
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
if (response.statusCode != 200 && response.statusCode != 201 && response.statusCode != 202) {
|
||||
throw Exception('Generate report failed: ${response.statusCode}');
|
||||
}
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: generateReport échoué', error: e, stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> exportReportPdf(String type) async {
|
||||
try {
|
||||
final response = await _apiClient.post(
|
||||
'$_analyticsBase/reports/export',
|
||||
queryParameters: {'type': type, 'format': 'pdf'},
|
||||
);
|
||||
if (response.statusCode == 200 && response.data is Map) {
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
// Le backend retourne l'URL du fichier PDF généré
|
||||
return data['url'] as String? ?? data['fileUrl'] as String? ?? '';
|
||||
}
|
||||
throw Exception('Export PDF failed: ${response.statusCode}');
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: exportReportPdf échoué', error: e, stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> exportReportExcel(String type, {String format = 'excel'}) async {
|
||||
try {
|
||||
final response = await _apiClient.post(
|
||||
'$_analyticsBase/reports/export',
|
||||
queryParameters: {'type': type, 'format': format},
|
||||
);
|
||||
if (response.statusCode == 200 && response.data is Map) {
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
// Le backend retourne l'URL du fichier Excel/CSV généré
|
||||
return data['url'] as String? ?? data['fileUrl'] as String? ?? '';
|
||||
}
|
||||
throw Exception('Export $format failed: ${response.statusCode}');
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: exportReportExcel échoué', error: e, stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> scheduleReport({String? cronExpression}) async {
|
||||
try {
|
||||
final response = await _apiClient.post(
|
||||
'$_analyticsBase/reports/schedule',
|
||||
data: cronExpression != null ? {'cronExpression': cronExpression} : null,
|
||||
);
|
||||
if (response.statusCode != 200 && response.statusCode != 201 && response.statusCode != 204) {
|
||||
throw Exception('Schedule report failed: ${response.statusCode}');
|
||||
}
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: scheduleReport échoué', error: e, stackTrace: st);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> getScheduledReports() async {
|
||||
try {
|
||||
final response = await _apiClient.get('$_analyticsBase/reports/scheduled');
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
if (data is List) {
|
||||
return data.map((e) => Map<String, dynamic>.from(e as Map)).toList();
|
||||
}
|
||||
}
|
||||
return [];
|
||||
} on DioException catch (e, st) {
|
||||
AppLogger.error('ReportsRepository: getScheduledReports échoué', error: e, stackTrace: st);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user