diff --git a/api_results.txt b/api_results.txt
new file mode 100644
index 0000000..c4a9654
--- /dev/null
+++ b/api_results.txt
@@ -0,0 +1,34 @@
+===========================================================================
+ RESULTATS DES TESTS API
+===========================================================================
+Date: 01/12/2025
+Heure: 17:01:04,29
+===========================================================================
+
+*** ENDPOINT !i! : Transactions journalieres (global) ***
+{"type":"transaction","period":"daily","startDate":"","endDate":"","totalAmount":600,"totalCount":38,"items":[{"period":"2025-11-14","totalAmount":600,"totalTax":600,"count":38,"successCount":9,"failedCount":29,"pendingCount":0}],"summary":{"avgAmount":15.79,"minAmount":600,"maxAmount":600},"generatedAt":"2025-12-01T17:01:03.406Z"}
+
+*** ENDPOINT !i! : Transactions journalieres (merchant 4) ***
+{"type":"transaction","period":"daily","startDate":"","endDate":"","merchantPartnerId":4,"totalAmount":600,"totalCount":38,"items":[{"period":"2025-11-14","totalAmount":600,"totalTax":600,"count":38,"successCount":9,"failedCount":29,"pendingCount":0,"merchantPartnerId":4}],"summary":{"avgAmount":15.79,"minAmount":600,"maxAmount":600},"generatedAt":"2025-12-01T17:01:03.885Z"}
+
+*** ENDPOINT !i! : Transactions hebdomadaires ***
+{"type":"transaction","period":"weekly","startDate":"","endDate":"","totalAmount":600,"totalCount":38,"items":[{"period":"2025-W46","totalAmount":600,"totalTax":600,"count":38,"successCount":9,"failedCount":29,"pendingCount":0}],"summary":{"avgAmount":15.79,"minAmount":600,"maxAmount":600},"generatedAt":"2025-12-01T17:01:04.313Z"}
+
+*** ENDPOINT !i! : Transactions mensuelles ***
+{"type":"transaction","period":"monthly","startDate":"","endDate":"","totalAmount":600,"totalCount":38,"items":[{"period":"2025-11","totalAmount":600,"totalTax":600,"count":38,"successCount":9,"failedCount":29,"pendingCount":0}],"summary":{"avgAmount":15.79,"minAmount":600,"maxAmount":600},"generatedAt":"2025-12-01T17:01:04.782Z"}
+
+*** ENDPOINT !i! : Transactions avec dates (01-30 nov 2024) ***
+{"type":"transaction","period":"daily","startDate":"2024-11-01","endDate":"2024-11-30","totalAmount":0,"totalCount":0,"items":[],"summary":{"avgAmount":0,"minAmount":0,"maxAmount":0},"generatedAt":"2025-12-01T17:01:05.230Z"}
+
+*** ENDPOINT !i! : Subscriptions journalieres ***
+{"type":"subscription","period":"daily","startDate":"","endDate":"","totalAmount":180,"totalCount":9,"items":[{"period":"2025-11-14","totalAmount":180,"count":9,"activeCount":9,"cancelledCount":0}],"summary":{"avgAmount":20,"minAmount":180,"maxAmount":180},"generatedAt":"2025-12-01T17:01:05.732Z"}
+
+*** ENDPOINT !i! : Subscriptions mensuelles (merchant 4) ***
+{"type":"subscription","period":"monthly","startDate":"","endDate":"","merchantPartnerId":4,"totalAmount":180,"totalCount":9,"items":[{"period":"2025-11","totalAmount":180,"count":9,"activeCount":9,"cancelledCount":0,"merchantPartnerId":4}],"summary":{"avgAmount":20,"minAmount":180,"maxAmount":180},"generatedAt":"2025-12-01T17:01:06.233Z"}
+
+*** ENDPOINT !i! : Synchronisation manuelle (POST) ***
+{"message":"Full sync completed successfully","timestamp":"2025-12-01T17:01:07.034Z"}
+
+===========================================================================
+ TESTS TERMINES - !i! endpoints
+===========================================================================
diff --git a/package-lock.json b/package-lock.json
index 4b51b4b..38b45b5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,7 +23,7 @@
"@fullcalendar/list": "^6.1.19",
"@fullcalendar/timegrid": "^6.1.19",
"@ng-bootstrap/ng-bootstrap": "^19.0.1",
- "@ng-icons/core": "^32.2.0",
+ "@ng-icons/core": "^32.5.0",
"@ng-icons/lucide": "^32.2.0",
"@ng-icons/tabler-icons": "^32.2.0",
"@popperjs/core": "^2.11.8",
@@ -3108,9 +3108,9 @@
}
},
"node_modules/@ng-icons/core": {
- "version": "32.2.0",
- "resolved": "https://registry.npmjs.org/@ng-icons/core/-/core-32.2.0.tgz",
- "integrity": "sha512-42S9QFH+FaigjXQp0QtWLHyJz8G8EaqJqcrK3qfZH4OyH86o32s3pkkf+lWnEtp2tR+07qNPSLYKCIgDE64Tug==",
+ "version": "32.5.0",
+ "resolved": "https://registry.npmjs.org/@ng-icons/core/-/core-32.5.0.tgz",
+ "integrity": "sha512-6zAXQ5vryaclOWEVzprFJjJAW6NSOl0eBm+I6BwmcMk+vR+1vHU82DNpNTbUE9Wn4CGXEP1yd+S+pTKIaRTXjg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
diff --git a/package.json b/package.json
index 0aa92f0..e2aad41 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"@fullcalendar/list": "^6.1.19",
"@fullcalendar/timegrid": "^6.1.19",
"@ng-bootstrap/ng-bootstrap": "^19.0.1",
- "@ng-icons/core": "^32.2.0",
+ "@ng-icons/core": "^32.5.0",
"@ng-icons/lucide": "^32.2.0",
"@ng-icons/tabler-icons": "^32.2.0",
"@popperjs/core": "^2.11.8",
diff --git a/src/app/modules/dcb-dashboard/components/active-subscriptions.ts b/src/app/modules/dcb-dashboard/components/active-subscriptions.ts
deleted file mode 100644
index 0586516..0000000
--- a/src/app/modules/dcb-dashboard/components/active-subscriptions.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { NgIconComponent } from '@ng-icons/core';
-import { NgbProgressbarModule } from '@ng-bootstrap/ng-bootstrap';
-import { CountUpModule } from 'ngx-countup';
-import { DcbReportingService, SubscriptionItem } from '../services/dcb-reporting.service';
-import { catchError, finalize } from 'rxjs/operators';
-import { of, Subscription } from 'rxjs';
-
-@Component({
- selector: 'app-active-subscriptions',
- imports: [CommonModule, NgIconComponent, NgbProgressbarModule, CountUpModule],
- template: `
-
-
-
-
-
-
-
-
-
-
-
Total actifs
-
-
- {{ formatNumber(totalSubscriptions) }}
-
-
-
-
-
-
-
-
-
-
-
- Taux d'activité
- {{ activePercentage | number:'1.1-1' }}%
-
-
-
-
-
-
- Nouveaux
-
{{ newSubscriptionsToday }}
-
-
- Annulés
-
{{ cancelledSubscriptions }}
-
-
-
-
-
-
-
-
-
- `,
- styles: [`
- .spin {
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- .alert-sm {
- padding: 0.375rem 0.75rem;
- font-size: 0.875rem;
- }
- `]
-})
-export class ActiveSubscriptions implements OnInit, OnDestroy {
- loading = false;
- error: string | null = null;
- lastUpdated = new Date();
-
- // Données d'abonnements
- totalSubscriptions = 0;
- newSubscriptionsToday = 0;
- cancelledSubscriptions = 0;
- activePercentage = 0;
-
- private apiSubscription?: Subscription;
-
- constructor(private reportingService: DcbReportingService) {}
-
- ngOnInit() {
- this.loadSubscriptionData();
- }
-
- ngOnDestroy() {
- if (this.apiSubscription) {
- this.apiSubscription.unsubscribe();
- }
- }
-
- loadSubscriptionData() {
- this.loading = true;
- this.error = null;
-
- const today = new Date();
- const startDate = this.reportingService.formatDate(today);
-
- console.log('ActiveSubscriptions - Loading data for date:', startDate);
-
- this.apiSubscription = this.reportingService.getDailySubscriptions(startDate, startDate)
- .pipe(
- catchError(err => {
- console.error('ActiveSubscriptions - API error:', err);
- this.error = 'Impossible de charger les données';
- return of([]);
- }),
- finalize(() => {
- this.loading = false;
- this.lastUpdated = new Date();
- })
- )
- .subscribe({
- next: (subscriptions: SubscriptionItem[]) => {
- console.log('ActiveSubscriptions - Received data:', subscriptions);
- this.processSubscriptionData(subscriptions);
- }
- });
- }
-
- processSubscriptionData(subscriptions: SubscriptionItem[]) {
- if (!subscriptions || subscriptions.length === 0) {
- console.warn('ActiveSubscriptions - No data available');
- return;
- }
-
- // Prendre les données du jour (ou la période la plus récente)
- const latestData = subscriptions[subscriptions.length - 1];
-
- console.log('ActiveSubscriptions - Latest data:', latestData);
-
- // Utiliser les données brutes de l'API
- // Note: Votre API retourne activeCount et cancelledCount
- this.totalSubscriptions = latestData.activeCount || 0;
- this.cancelledSubscriptions = latestData.cancelledCount || 0;
-
- // Pour les nouveaux abonnements aujourd'hui
- // Si l'API ne fournit pas cette donnée, on peut estimer ou utiliser 0
- this.newSubscriptionsToday = 0; // À adapter selon votre logique
-
- // Calculer le pourcentage d'activité
- const totalCount = latestData.count || 0;
- this.activePercentage = totalCount > 0 ?
- ((latestData.activeCount || 0) / totalCount) * 100 : 0;
- }
-
- formatNumber(value: number): string {
- return new Intl.NumberFormat('fr-FR').format(value);
- }
-
- refresh() {
- console.log('ActiveSubscriptions - Refreshing data');
-
- // Essayer de vider le cache si disponible
- if (this.reportingService.clearCache) {
- this.reportingService.clearCache();
- }
-
- this.loadSubscriptionData();
- }
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/dcb-dashboard-report.ts b/src/app/modules/dcb-dashboard/components/dcb-dashboard-report.ts
deleted file mode 100644
index 3b6ca83..0000000
--- a/src/app/modules/dcb-dashboard/components/dcb-dashboard-report.ts
+++ /dev/null
@@ -1,617 +0,0 @@
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { Chartjs } from '@app/components/chartjs';
-import { ChartConfiguration } from 'chart.js';
-import { getColor } from '@/app/utils/color-utils';
-import { NgbProgressbarModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
-import { NgIconComponent } from '@ng-icons/core';
-import { CountUpModule } from 'ngx-countup';
-import { DcbReportingService, TransactionItem, SubscriptionItem } from '../services/dcb-reporting.service';
-import { catchError } from 'rxjs/operators';
-import { of, Subscription } from 'rxjs';
-
-@Component({
- selector: 'app-dashboard-report',
- imports: [
- CommonModule,
- Chartjs,
- NgbProgressbarModule,
- NgbDropdownModule, // Ajoutez ce module
- NgIconComponent,
- CountUpModule
- ],
- template: `
-
-
-
-
-
-
Dashboard Reporting
-
-
-
-
-
-
-
-
-
-
-
-
-
Transactions Journalières
-
-
- {{ dailyTransactions.total }}
-
-
-
Transactions
-
- {{ formatCurrency(dailyTransactions.revenue) }}
- = 95 ? 'text-success' : 'text-warning'">
-
- {{ dailyTransactions.successRate | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
-
-
Transactions Hebdomadaires
-
-
- {{ weeklyTransactions.total }}
-
-
-
Transactions
-
- {{ formatCurrency(weeklyTransactions.revenue) }}
- = 95 ? 'text-success' : 'text-warning'">
-
- {{ weeklyTransactions.successRate | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
-
-
Transactions Mensuelles
-
-
- {{ monthlyTransactions.total }}
-
-
-
Transactions
-
- {{ formatCurrency(monthlyTransactions.revenue) }}
- = 95 ? 'text-success' : 'text-warning'">
-
- {{ monthlyTransactions.successRate | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
-
-
Taux global
-
-
- {{ overallSuccessRate | number:'1.1-1' }}
- %
-
-
Succès (30 jours)
-
- Période: 30j
- = 95 ? 'text-success' : overallSuccessRate >= 90 ? 'text-warning' : 'text-danger'">
- {{ getPerformanceLabel(overallSuccessRate) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Journaliers
-
-
-
-
{{ dailySubscriptions.active }}
- Actifs
-
-
- Total: {{ dailySubscriptions.total }}
- Annulés: {{ dailySubscriptions.cancelled }}
-
-
-
-
-
-
Hebdomadaires
-
-
-
-
{{ weeklySubscriptions.active }}
- Actifs
-
-
- Total: {{ weeklySubscriptions.total }}
- Annulés: {{ weeklySubscriptions.cancelled }}
-
-
-
-
-
-
Mensuels
-
-
-
-
{{ monthlySubscriptions.active }}
- Actifs
-
-
- Total: {{ monthlySubscriptions.total }}
- Annulés: {{ monthlySubscriptions.cancelled }}
-
-
-
-
-
-
-
- Taux d'activité
- {{ getCurrentActivityRate() | number:'1.1-1' }}%
-
-
-
- {{ getCurrentActiveSubscriptions() }} actifs sur {{ getCurrentTotalSubscriptions() }} total
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Période |
- Transactions |
- Montant Total |
- Réussies |
- Échouées |
- En attente |
- Taux de succès |
-
-
-
-
- | {{ item.period }} |
- {{ item.count }} |
- {{ formatCurrency(item.totalAmount) }} |
- {{ item.successCount }} |
- {{ item.failedCount }} |
- {{ item.pendingCount }} |
-
- = 95 ? 'text-success' : getSuccessRate(item) >= 90 ? 'text-warning' : 'text-danger'">
- {{ getSuccessRate(item) | number:'1.1-1' }}%
-
- |
-
-
- |
- Aucune donnée de transaction disponible
- |
-
-
-
-
-
-
-
-
-
- `,
- styles: [`
- .spin {
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- `]
-})
-export class DashboardReport implements OnInit, OnDestroy {
-
- // Périodes d'affichage
- subscriptionPeriod: 'daily' | 'weekly' | 'monthly' = 'daily';
- transactionPeriod: 'daily' | 'weekly' | 'monthly' = 'daily';
-
- // Données des transactions
- dailyTransactions = { total: 0, revenue: 0, successRate: 0 };
- weeklyTransactions = { total: 0, revenue: 0, successRate: 0 };
- monthlyTransactions = { total: 0, revenue: 0, successRate: 0 };
- overallSuccessRate = 0;
-
- // Données brutes pour les tableaux
- dailyTransactionItems: TransactionItem[] = [];
- weeklyTransactionItems: TransactionItem[] = [];
- monthlyTransactionItems: TransactionItem[] = [];
-
- // Données des abonnements
- dailySubscriptions = { total: 0, active: 0, cancelled: 0 };
- weeklySubscriptions = { total: 0, active: 0, cancelled: 0 };
- monthlySubscriptions = { total: 0, active: 0, cancelled: 0 };
-
- // Données brutes pour les abonnements
- dailySubscriptionItems: SubscriptionItem[] = [];
- weeklySubscriptionItems: SubscriptionItem[] = [];
- monthlySubscriptionItems: SubscriptionItem[] = [];
-
- // Données pour le graphique
- chartData: { date: string; revenue: number }[] = [];
-
- // Abonnements aux API
- private subscriptions: Subscription[] = [];
-
- constructor(private reportingService: DcbReportingService) {}
-
- ngOnInit() {
-
- }
-
- ngAfterViewInit() {
- // Charger les données après que la vue soit initialisée
- this.loadAllData();
- }
-
- ngOnDestroy() {
- this.cleanupSubscriptions();
- }
-
- private cleanupSubscriptions() {
- this.subscriptions.forEach(sub => sub.unsubscribe());
- this.subscriptions = [];
- }
-
- loadAllData() {
- this.cleanupSubscriptions();
-
- // Charger toutes les données en parallèle
- this.subscriptions.push(
- this.reportingService.getDailyTransactions()
- .pipe(catchError(() => of([])))
- .subscribe(data => {
- this.dailyTransactionItems = data;
- this.dailyTransactions = this.calculateTransactionStats(data);
- })
- );
-
- this.subscriptions.push(
- this.reportingService.getWeeklyTransactions()
- .pipe(catchError(() => of([])))
- .subscribe(data => {
- this.weeklyTransactionItems = data;
- this.weeklyTransactions = this.calculateTransactionStats(data);
- })
- );
-
- this.subscriptions.push(
- this.reportingService.getMonthlyTransactions()
- .pipe(catchError(() => of([])))
- .subscribe(data => {
- this.monthlyTransactionItems = data;
- this.monthlyTransactions = this.calculateTransactionStats(data);
- this.overallSuccessRate = this.monthlyTransactions.successRate;
- this.prepareChartData(data);
- })
- );
-
- this.subscriptions.push(
- this.reportingService.getDailySubscriptions()
- .pipe(catchError(() => of([])))
- .subscribe(data => {
- this.dailySubscriptionItems = data;
- this.dailySubscriptions = this.calculateSubscriptionStats(data);
- })
- );
-
- this.subscriptions.push(
- this.reportingService.getWeeklySubscriptions()
- .pipe(catchError(() => of([])))
- .subscribe(data => {
- this.weeklySubscriptionItems = data;
- this.weeklySubscriptions = this.calculateSubscriptionStats(data);
- })
- );
-
- this.subscriptions.push(
- this.reportingService.getMonthlySubscriptions()
- .pipe(catchError(() => of([])))
- .subscribe(data => {
- this.monthlySubscriptionItems = data;
- this.monthlySubscriptions = this.calculateSubscriptionStats(data);
- })
- );
- }
-
- // Méthodes pour changer la période d'affichage
- showDailySubscriptions() {
- this.subscriptionPeriod = 'daily';
- }
-
- showWeeklySubscriptions() {
- this.subscriptionPeriod = 'weekly';
- }
-
- showMonthlySubscriptions() {
- this.subscriptionPeriod = 'monthly';
- }
-
- showDailyTransactions() {
- this.transactionPeriod = 'daily';
- }
-
- showWeeklyTransactions() {
- this.transactionPeriod = 'weekly';
- }
-
- showMonthlyTransactions() {
- this.transactionPeriod = 'monthly';
- }
-
- // Méthodes utilitaires
- calculateTransactionStats(data: TransactionItem[]): { total: number; revenue: number; successRate: number } {
- if (!data || data.length === 0) {
- return { total: 0, revenue: 0, successRate: 0 };
- }
-
- const total = data.reduce((sum, item) => sum + (item.count || 0), 0);
- const revenue = data.reduce((sum, item) => sum + (item.totalAmount || 0), 0);
- const successful = data.reduce((sum, item) => sum + (item.successCount || 0), 0);
-
- const successRate = total > 0 ? (successful / total) * 100 : 0;
-
- return { total, revenue, successRate };
- }
-
- calculateSubscriptionStats(data: SubscriptionItem[]): { total: number; active: number; cancelled: number } {
- if (!data || data.length === 0) {
- return { total: 0, active: 0, cancelled: 0 };
- }
-
- // Prendre la dernière période
- const latest = data[data.length - 1];
-
- return {
- total: latest.count || 0,
- active: latest.activeCount || 0,
- cancelled: latest.cancelledCount || 0
- };
- }
-
- getSubscriptionPeriodLabel(): string {
- switch (this.subscriptionPeriod) {
- case 'daily': return 'Journalier';
- case 'weekly': return 'Hebdomadaire';
- case 'monthly': return 'Mensuel';
- default: return 'Période';
- }
- }
-
- getTransactionPeriodLabel(): string {
- switch (this.transactionPeriod) {
- case 'daily': return 'Journalières';
- case 'weekly': return 'Hebdomadaires';
- case 'monthly': return 'Mensuelles';
- default: return 'Période';
- }
- }
-
- prepareChartData(data: TransactionItem[]) {
- if (!data || data.length === 0) {
- this.chartData = [];
- return;
- }
-
- // Prendre les 6 derniers mois pour le graphique
- this.chartData = data
- .slice(-6)
- .map(item => ({
- date: item.period, // Ex: "2025-11"
- revenue: item.totalAmount || 0
- }));
- }
-
- getCurrentTransactions(): TransactionItem[] {
- switch (this.transactionPeriod) {
- case 'daily': return this.dailyTransactionItems;
- case 'weekly': return this.weeklyTransactionItems;
- case 'monthly': return this.monthlyTransactionItems;
- default: return this.dailyTransactionItems;
- }
- }
-
- getCurrentTotalSubscriptions(): number {
- switch (this.subscriptionPeriod) {
- case 'daily': return this.dailySubscriptions.total;
- case 'weekly': return this.weeklySubscriptions.total;
- case 'monthly': return this.monthlySubscriptions.total;
- default: return this.dailySubscriptions.total;
- }
- }
-
- getCurrentActiveSubscriptions(): number {
- switch (this.subscriptionPeriod) {
- case 'daily': return this.dailySubscriptions.active;
- case 'weekly': return this.weeklySubscriptions.active;
- case 'monthly': return this.monthlySubscriptions.active;
- default: return this.dailySubscriptions.active;
- }
- }
-
- getCurrentActivityRate(): number {
- const total = this.getCurrentTotalSubscriptions();
- const active = this.getCurrentActiveSubscriptions();
- return total > 0 ? (active / total) * 100 : 0;
- }
-
- getSuccessRate(item: TransactionItem): number {
- const count = item.count || 0;
- const success = item.successCount || 0;
- return count > 0 ? (success / count) * 100 : 0;
- }
-
- getPerformanceLabel(successRate: number): string {
- if (successRate >= 95) return 'Excellent';
- if (successRate >= 90) return 'Bon';
- if (successRate >= 80) return 'Moyen';
- if (successRate >= 70) return 'Passable';
- return 'À améliorer';
- }
-
- formatCurrency(amount: number): string {
- if (amount >= 1000000) {
- return `${(amount / 1000000).toFixed(1)}M XOF`;
- } else if (amount >= 1000) {
- return `${(amount / 1000).toFixed(0)}K XOF`;
- }
- return `${Math.round(amount)} XOF`;
- }
-
- revenueChart = (): ChartConfiguration => ({
- type: 'bar',
- data: {
- labels: this.chartData.map(item => {
- // Gérer différents formats de période
- if (item.date.includes('-')) {
- const [year, month] = item.date.split('-');
- const months = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'];
- const monthIndex = parseInt(month) - 1;
- if (monthIndex >= 0 && monthIndex < months.length) {
- return `${months[monthIndex]} ${year}`;
- }
- }
- return item.date;
- }),
- datasets: [
- {
- label: 'Revenue (XOF)',
- data: this.chartData.map(item => Math.round(item.revenue / 1000)), // En milliers
- backgroundColor: this.chartData.map((_, index) =>
- getColor(index === this.chartData.length - 1 ? 'chart-primary' : 'chart-secondary')
- ),
- borderRadius: 6,
- borderSkipped: false,
- }
- ]
- },
- options: {
- responsive: true,
- plugins: {
- legend: { display: false },
- tooltip: {
- callbacks: {
- label: (context) => {
- const value = context.raw as number;
- return `Revenue: ${this.formatCurrency(value * 1000)}`;
- }
- }
- }
- },
- scales: {
- x: {
- grid: { display: false }
- },
- y: {
- beginAtZero: true,
- ticks: {
- callback: (value) => `${value}K XOF`
- },
- title: {
- display: true,
- text: 'Revenue (en milliers XOF)'
- }
- }
- }
- }
- });
-
- refreshAll() {
- this.loadAllData();
- }
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.css b/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.css
new file mode 100644
index 0000000..1cebb85
--- /dev/null
+++ b/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.css
@@ -0,0 +1,212 @@
+/* Styles pour le dashboard DCB Reporting */
+.dashboard-container {
+ min-height: 100vh;
+ background-color: #f8f9fa;
+ padding: 1.5rem;
+}
+
+/* Header */
+.dashboard-header {
+ background: white;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
+}
+
+/* Barre d'état */
+.status-bar .bg-light {
+ background-color: #f8f9fa !important;
+ border: 1px solid #e9ecef;
+}
+
+/* Cartes KPI */
+.kpi-card {
+ border: none;
+ border-radius: 0.75rem;
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
+ transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.kpi-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
+}
+
+.kpi-card .avatar-sm {
+ width: 40px;
+ height: 40px;
+}
+
+.kpi-card .border-start {
+ border-left-width: 4px !important;
+}
+
+/* Graphiques */
+.card {
+ border: none;
+ border-radius: 1rem;
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
+ margin-bottom: 1rem;
+}
+
+.card-header {
+ background-color: white;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ padding: 1.25rem 1.5rem;
+}
+
+.card-body {
+ padding: 1.5rem;
+}
+
+/* Tables */
+.table-responsive {
+ border-radius: 0.75rem;
+ overflow: hidden;
+}
+
+.table {
+ margin-bottom: 0;
+}
+
+.table thead {
+ background-color: #f8f9fa;
+}
+
+.table tbody tr {
+ transition: background-color 0.15s;
+}
+
+.table tbody tr:hover {
+ background-color: rgba(13, 110, 253, 0.05);
+}
+
+/* Alertes */
+.alert-list {
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.alert-item {
+ transition: background-color 0.15s;
+}
+
+.alert-item:hover {
+ background-color: rgba(0, 0, 0, 0.02);
+}
+
+/* Indicateurs de santé */
+.health-indicator {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+}
+
+.health-indicator.indicator-success {
+ background-color: #51cf66;
+ box-shadow: 0 0 0 2px rgba(81, 207, 102, 0.2);
+}
+
+.health-indicator.indicator-warning {
+ background-color: #ff922b;
+ box-shadow: 0 0 0 2px rgba(255, 146, 43, 0.2);
+}
+
+.health-indicator.indicator-danger {
+ background-color: #ff6b6b;
+ box-shadow: 0 0 0 2px rgba(255, 107, 107, 0.2);
+}
+
+/* Spinners */
+.spin {
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* Badges */
+.badge {
+ padding: 0.35em 0.65em;
+ font-weight: 500;
+}
+
+/* Dropdown */
+.dropdown-menu {
+ border: none;
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
+ border-radius: 0.75rem;
+ padding: 0.5rem 0;
+}
+
+.dropdown-item {
+ padding: 0.5rem 1rem;
+ transition: background-color 0.15s;
+}
+
+.dropdown-item:hover {
+ background-color: rgba(13, 110, 253, 0.1);
+}
+
+/* Form controls */
+.input-group-sm {
+ max-width: 300px;
+}
+
+.form-control {
+ border-color: #e9ecef;
+}
+
+.form-control:focus {
+ border-color: #86b7fe;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+
+/* Boutons */
+.btn {
+ border-radius: 0.5rem;
+ font-weight: 500;
+}
+
+.btn-outline-primary {
+ border-color: #0d6efd;
+ color: #0d6efd;
+}
+
+.btn-outline-primary:hover {
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+
+/* Dashboard footer */
+.dashboard-footer .card {
+ background-color: #f8f9fa;
+ border: 1px solid #e9ecef;
+ border-radius: 0.75rem;
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .dashboard-container {
+ padding: 1rem;
+ }
+
+ .dashboard-header {
+ padding: 1rem;
+ }
+
+ .status-bar .col-auto {
+ margin-bottom: 0.5rem;
+ }
+
+ .filters-card .input-group {
+ max-width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.html b/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.html
new file mode 100644
index 0000000..4bdc5ce
--- /dev/null
+++ b/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.html
@@ -0,0 +1,601 @@
+
+
+
+
+
+
+
+
+
+
+ Mis à jour: {{ lastUpdated | date:'HH:mm:ss' }}
+
+
+
+
+
+ Services: {{ stats.onlineServices }}/{{ stats.totalServices }} en ligne
+
+
+
+
+
+ Opérateur: Orange
+
+
+
+
+
+
+
+
+
+
+
{{ syncResponse.message }}
+
Synchronisée à {{ formatDate(syncResponse.timestamp) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Transactions
+ {{ formatNumber(getPaymentStats().daily.transactions) }}
+ Journalier
+
+
+
+
+
+
+ {{ formatCurrency(getPaymentStats().daily.revenue) }}
+
+
+ {{ getPaymentStats().daily.successRate | number:'1.0-0' }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
Transactions
+ {{ formatNumber(getPaymentStats().weekly.transactions) }}
+ Hebdomadaire
+
+
+
+
+
+
+ {{ formatCurrency(getPaymentStats().weekly.revenue) }}
+
+
+ {{ getPaymentStats().weekly.successRate | number:'1.0-0' }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
Transactions
+ {{ formatNumber(getPaymentStats().monthly.transactions) }}
+ Mensuel
+
+
+
+
+
+
+ {{ formatCurrency(getPaymentStats().monthly.revenue) }}
+
+
+ {{ getPaymentStats().monthly.successRate | number:'1.0-0' }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
Revenue {{ currentYear }}
+ {{ formatCurrency(stats.yearlyRevenue) }}
+ Annuel
+
+
+
+
+
+
+ {{ formatNumber(stats.yearlyTransactions) }} transactions
+
+
+ {{ currentYear }}
+
+
+
+
+
+
+
+
+
+
+
+
+
Abonnements
+ {{ formatNumber(getSubscriptionStats().active) }}
+ Actifs
+
+
+
+
+
+
+ Total: {{ formatNumber(getSubscriptionStats().total) }}
+
+
+ +{{ getSubscriptionStats().newToday }}
+
+
+
+
+
+
+
+
+
+
+
+
+
Taux de succès
+ {{ stats.successRate | number:'1.1-1' }}%
+ Global
+
+
+
+
+
+
+ {{ getPerformanceLabel(stats.successRate) }}
+
+
+ {{ stats.avgSuccessRate | number:'1.0-0' }}% cible
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Chargement des données...
+
+
+
+
Aucune donnée disponible
+
+
0"
+ class="position-relative" style="height: 300px;">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ stats.successRate | number:'1.0-0' }}%
+
+ Taux de succès
+
+
+
+
+
+
+
+
+
+ {{ formatNumber(dailyTransactions?.items?.[0]?.successCount || 0) }}
+
+
Réussies
+
+
+
+
+
+ {{ formatNumber(dailyTransactions?.items?.[0]?.failedCount || 0) }}
+
+
Échouées
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ service.service }}
+
+
+
+ {{ service.responseTime }}ms
+
+
+ {{ service.status }}
+
+
+
+
+
+
+
+
+ Dernière vérification: {{ formatTimeAgo(systemHealth[0]?.lastChecked) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Date/Heure |
+ Montant |
+ Statut |
+ Taux |
+
+
+
+
+ |
+ {{ item.period }}
+ {{ formatNumber(item.count) }} transactions
+ |
+
+ {{ formatCurrency(item.totalAmount) }}
+ |
+
+
+ {{ item.successCount || 0 }} ✓
+
+
+ {{ item.failedCount || 0 }} ✗
+
+ |
+
+ 0 ? (item.successCount / item.count) * 100 : 0))">
+ {{ (item.count > 0 ? (item.successCount / item.count) * 100 : 0) | number:'1.1-1' }}%
+
+ |
+
+
+ |
+
+ Aucune transaction disponible
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ alert.title }}
+ {{ formatTimeAgo(alert.timestamp) }}
+
+
{{ alert.description }}
+
+
+
+
+
+
Aucune alerte active
+
Tous les systèmes fonctionnent normalement
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.ts b/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.ts
new file mode 100644
index 0000000..65c4b6e
--- /dev/null
+++ b/src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.ts
@@ -0,0 +1,1069 @@
+import { Component, OnInit, OnDestroy, AfterViewInit, ChangeDetectorRef, inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { catchError, finalize, tap } from 'rxjs/operators';
+import { of, Subscription, forkJoin, Observable } from 'rxjs';
+
+import { Chart, ChartConfiguration, registerables } from 'chart.js';
+import { ViewChild, ElementRef } from '@angular/core';
+import { NgIconComponent } from '@ng-icons/core';
+
+// Import des icônes Lucide
+import { NgIcon } from '@ng-icons/core';
+
+// Import du SERVICE plutôt que de refaire les requêtes HTTP
+import {
+ DcbReportingService,
+ TransactionReport,
+ SubscriptionReport,
+ SyncResponse,
+ ReportParams
+} from '../services/dcb-reporting.service';
+
+interface ServiceHealth {
+ service: string;
+ status: string;
+ color: string;
+ url: string;
+ responseTime?: number;
+ lastChecked: Date;
+}
+
+interface Alert {
+ type: 'warning' | 'info' | 'success' | 'danger';
+ title: string;
+ description: string;
+ time: string;
+ timestamp: Date;
+}
+
+@Component({
+ selector: 'app-dcb-reporting-dashboard',
+ templateUrl: './dcb-reporting-dashboard.html',
+ styleUrls: ['./dcb-reporting-dashboard.css'],
+ standalone: true,
+ imports: [CommonModule,NgIcon, FormsModule, NgIconComponent],
+
+})
+export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
+ private subscriptions: Subscription[] = [];
+ private reportingService = inject(DcbReportingService);
+ private cdr = inject(ChangeDetectorRef);
+
+ // ============ DONNÉES ============
+ dailyTransactions: TransactionReport | null = null;
+ merchantTransactions: TransactionReport | null = null;
+ weeklyTransactions: TransactionReport | null = null;
+ monthlyTransactions: TransactionReport | null = null;
+ yearlyTransactions: TransactionReport | null = null;
+ transactionsWithDates: TransactionReport | null = null;
+
+ dailySubscriptions: SubscriptionReport | null = null;
+ merchantSubscriptions: SubscriptionReport | null = null;
+
+ syncResponse: SyncResponse | null = null;
+
+ // ============ PARAMÈTRES ============
+ merchantId: number = 4;
+ startDate: string = new Date().toISOString().split('T')[0];
+ endDate: string = new Date().toISOString().split('T')[0];
+ currentYear = new Date().getFullYear();
+ selectedPeriod: string = '7days';
+
+ // ============ ÉTATS ============
+ loading = {
+ all: false,
+ dailyTransactions: false,
+ merchantTransactions: false,
+ weeklyTransactions: false,
+ monthlyTransactions: false,
+ yearlyTransactions: false,
+ transactionsWithDates: false,
+ dailySubscriptions: false,
+ merchantSubscriptions: false,
+ sync: false,
+ healthCheck: false
+ };
+
+ errors: { [key: string]: string } = {};
+ lastUpdated = new Date();
+
+ // ============ SANTÉ DU SYSTÈME ============
+ systemHealth: ServiceHealth[] = [
+ {
+ service: 'Transactions Quotidiennes',
+ status: 'Checking...',
+ color: 'warning',
+ url: 'transactions/daily',
+ lastChecked: new Date()
+ },
+ {
+ service: 'Transactions Hebdomadaires',
+ status: 'Checking...',
+ color: 'warning',
+ url: 'transactions/weekly',
+ lastChecked: new Date()
+ },
+ {
+ service: 'Transactions Mensuelles',
+ status: 'Checking...',
+ color: 'warning',
+ url: 'transactions/monthly',
+ lastChecked: new Date()
+ },
+ {
+ service: 'Abonnements Quotidiens',
+ status: 'Checking...',
+ color: 'warning',
+ url: 'subscriptions/daily',
+ lastChecked: new Date()
+ },
+ {
+ service: 'Abonnements Mensuels',
+ status: 'Checking...',
+ color: 'warning',
+ url: 'subscriptions/monthly',
+ lastChecked: new Date()
+ },
+ {
+ service: 'Synchronisation',
+ status: 'Checking...',
+ color: 'warning',
+ url: 'sync/full',
+ lastChecked: new Date()
+ }
+ ];
+
+ alerts: Alert[] = [];
+
+ revenueChartData: any[] = [];
+ successChartData: any[] = [];
+ operatorData = [
+ { name: 'Orange', rate: 98.5, color: 'success' }
+ ];
+
+ stats = {
+ totalRevenue: 0,
+ totalTransactions: 0,
+ totalSubscriptions: 0,
+ successRate: 0,
+ activeSubscriptions: 0,
+ avgTransaction: 0,
+ maxRevenueDay: 0,
+ avgSuccessRate: 98.5,
+ yearlyRevenue: 0,
+ yearlyTransactions: 0,
+ totalServices: 0,
+ onlineServices: 0,
+ offlineServices: 0
+ };
+
+ @ViewChild('successRateChart') successRateChartRef!: ElementRef;
+ @ViewChild('revenueChart') revenueChartRef!: ElementRef;
+ private successRateChart: Chart | null = null;
+ private revenueChart: Chart | null = null;
+
+ constructor() {
+ Chart.register(...registerables);
+ }
+
+ ngOnInit(): void {
+ // Charger les données immédiatement
+ this.loadAllData();
+ this.checkSystemHealth();
+ }
+
+ ngAfterViewInit(): void {
+ // Initialiser les graphiques après que la vue soit rendue
+ setTimeout(() => {
+ this.initCharts();
+ }, 100);
+ }
+
+ ngOnDestroy(): void {
+ this.subscriptions.forEach(sub => sub.unsubscribe());
+ if (this.successRateChart) {
+ this.successRateChart.destroy();
+ }
+ if (this.revenueChart) {
+ this.revenueChart.destroy();
+ }
+ }
+
+ // ============ MÉTHODES AJOUTÉES POUR LE TEMPLATE ============
+ loadDailySubscriptionsObservable(): Observable {
+ return this.loadDailySubscriptions();
+ }
+
+ loadMerchantSubscriptionsObservable(): Observable {
+ return this.loadMerchantSubscriptions();
+ }
+
+ loadDailyTransactionsObservable(): Observable {
+ return this.loadDailyTransactions();
+ }
+
+ getAlertBadgeClass(): string {
+ if (this.alerts.length === 0) return 'bg-success';
+ if (this.alerts.filter(a => a.type === 'danger').length > 0) return 'bg-danger';
+ if (this.alerts.filter(a => a.type === 'warning').length > 0) return 'bg-warning';
+ return 'bg-info';
+ }
+
+ getTotalSuccessCount(): number {
+ if (!this.dailyTransactions?.items) return 0;
+ return this.dailyTransactions.items.reduce((sum: number, item) =>
+ sum + (item.successCount || 0), 0);
+ }
+
+ getTotalActiveCount(): number {
+ if (!this.merchantSubscriptions?.items) return 0;
+ return this.merchantSubscriptions.items.reduce((sum: number, item) =>
+ sum + (item.activeCount || 0), 0);
+ }
+
+ getActiveRate(): number {
+ if (!this.merchantSubscriptions?.totalCount) return 0;
+ return (this.getTotalActiveCount() / this.merchantSubscriptions.totalCount) * 100;
+ }
+
+ getOverallSuccessRate(): number {
+ const totalTransactions = this.getTotalSuccessCount() +
+ (this.dailyTransactions?.items?.reduce((sum: number, item) =>
+ sum + (item.failedCount || 0), 0) || 0);
+
+ if (totalTransactions === 0) return 0;
+ return (this.getTotalSuccessCount() / totalTransactions) * 100;
+ }
+
+ formatTimeAgo(date: Date | undefined): string {
+ if (!date) return 'Jamais';
+
+ const now = new Date();
+ const diffMs = now.getTime() - date.getTime();
+ const diffMins = Math.floor(diffMs / 60000);
+
+ if (diffMins < 1) return 'À l\'instant';
+ if (diffMins < 60) return `Il y a ${diffMins} min`;
+ if (diffMins < 1440) return `Il y a ${Math.floor(diffMins / 60)} h`;
+ return `Il y a ${Math.floor(diffMins / 1440)} j`;
+ }
+
+ // ============ VÉRIFICATION DE SANTÉ DES ENDPOINTS ============
+ checkSystemHealth(): void {
+ this.loading.healthCheck = true;
+ this.alerts = [];
+ this.cdr.detectChanges();
+
+ const healthChecks = this.systemHealth.map(service =>
+ this.checkEndpointHealth(service.url, service.service)
+ );
+
+ forkJoin(healthChecks).subscribe({
+ next: (results) => {
+ this.updateSystemHealthStatus(results);
+ this.generateAlertsBasedOnHealth();
+ this.calculateHealthStats();
+ this.loading.healthCheck = false;
+ this.cdr.detectChanges();
+ },
+ error: () => {
+ this.loading.healthCheck = false;
+ this.addAlert('danger', 'Erreur de vérification',
+ 'Impossible de vérifier la santé des services', 'Maintenant');
+ this.cdr.detectChanges();
+ }
+ });
+ }
+
+ private checkEndpointHealth(endpoint: string, serviceName: string) {
+ if (endpoint === 'sync/full') {
+ return this.reportingService.syncHealthCheck().pipe(
+ catchError(err => {
+ return of({
+ service: serviceName,
+ endpoint: endpoint,
+ status: 'error',
+ statusCode: err.status || 0,
+ error: err.message
+ });
+ })
+ );
+ }
+
+ return this.reportingService.healthCheck(endpoint).pipe(
+ catchError(err => {
+ return of({
+ service: serviceName,
+ endpoint: endpoint,
+ status: 'error',
+ statusCode: err.status || 0,
+ error: err.message
+ });
+ })
+ );
+ }
+
+ private updateSystemHealthStatus(results: any[]): void {
+ results.forEach((result, index) => {
+ const service = this.systemHealth[index];
+
+ if (result.status === 'success') {
+ service.status = 'Online';
+ service.color = 'success';
+ } else {
+ service.status = 'Offline';
+ service.color = 'danger';
+ this.addAlert('danger', `${service.service} hors ligne`,
+ `Impossible de contacter le service ${service.service}`, 'Maintenant');
+ }
+
+ service.lastChecked = new Date();
+ });
+ }
+
+ private generateAlertsBasedOnHealth(): void {
+ const offlineServices = this.systemHealth.filter(s => s.status === 'Offline');
+
+ if (offlineServices.length > 0) {
+ const servicesList = offlineServices.map(s => s.service).join(', ');
+ this.addAlert('danger', 'Services hors ligne',
+ `${offlineServices.length} service(s) hors ligne: ${servicesList}`, 'Maintenant');
+ }
+
+ const onlineServices = this.systemHealth.filter(s => s.status === 'Online').length;
+ if (onlineServices === this.systemHealth.length) {
+ this.addAlert('success', 'Tous les services opérationnels',
+ 'Tous les endpoints sont disponibles et répondent correctement', 'Maintenant');
+ }
+ }
+
+ private calculateHealthStats(): void {
+ this.stats.totalServices = this.systemHealth.length;
+ this.stats.onlineServices = this.systemHealth.filter(s => s.status === 'Online').length;
+ this.stats.offlineServices = this.systemHealth.filter(s => s.status === 'Offline').length;
+ }
+
+ private addAlert(type: 'warning' | 'info' | 'success' | 'danger',
+ title: string,
+ description: string,
+ time: string): void {
+ if (this.alerts.length >= 5) {
+ this.alerts.shift();
+ }
+
+ this.alerts.push({
+ type,
+ title,
+ description,
+ time,
+ timestamp: new Date()
+ });
+ }
+
+ // ============ CHARGEMENT DES DONNÉES ============
+ loadAllData(): void {
+ this.loading.all = true;
+ this.lastUpdated = new Date();
+ this.cdr.detectChanges();
+
+ const requests = [
+ this.loadDailyTransactions(),
+ this.loadMerchantTransactions(),
+ this.loadWeeklyTransactions(),
+ this.loadMonthlyTransactions(),
+ this.loadYearlyTransactions(),
+ this.loadTransactionsWithDates(),
+ this.loadDailySubscriptions(),
+ this.loadMerchantSubscriptions()
+ ];
+
+ this.subscriptions.push(
+ forkJoin(requests).subscribe({
+ next: () => {
+ this.loading.all = false;
+ this.calculateStats();
+ this.prepareChartData();
+ this.initCharts();
+ this.cdr.detectChanges();
+ },
+ error: (err) => {
+ console.error('Error loading all data:', err);
+ this.loading.all = false;
+ this.addAlert('danger', 'Erreur de chargement',
+ 'Impossible de charger toutes les données', 'Maintenant');
+ this.cdr.detectChanges();
+ }
+ })
+ );
+ }
+
+ // ============ MÉTHODES DE CHARGEMENT ============
+ private loadDailyTransactions(): Observable {
+ this.loading.dailyTransactions = true;
+ this.errors['dailyTransactions'] = '';
+ this.cdr.detectChanges();
+
+ return this.reportingService.getDailyTransactions().pipe(
+ catchError(err => {
+ this.errors['dailyTransactions'] = err.message || 'Erreur de chargement';
+ console.error('Error loading daily transactions:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidTransactionReport(data)) {
+ this.dailyTransactions = data;
+ } else {
+ this.dailyTransactions = null;
+ if (data !== null) {
+ this.errors['dailyTransactions'] = 'Format de données invalide';
+ }
+ }
+ }),
+ finalize(() => {
+ this.loading.dailyTransactions = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ private loadMerchantTransactions(): Observable {
+ this.loading.merchantTransactions = true;
+ this.errors['merchantTransactions'] = '';
+ this.cdr.detectChanges();
+
+ const params: ReportParams = {
+ merchantPartnerId: this.merchantId
+ };
+
+ return this.reportingService.getDailyTransactions(params).pipe(
+ catchError(err => {
+ this.errors['merchantTransactions'] = err.message || 'Erreur de chargement';
+ console.error('Error loading merchant transactions:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidTransactionReport(data)) {
+ this.merchantTransactions = data;
+ } else {
+ this.merchantTransactions = null;
+ }
+ }),
+ finalize(() => {
+ this.loading.merchantTransactions = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ private loadWeeklyTransactions(): Observable {
+ this.loading.weeklyTransactions = true;
+ this.errors['weeklyTransactions'] = '';
+ this.cdr.detectChanges();
+
+ return this.reportingService.getWeeklyTransactions().pipe(
+ catchError(err => {
+ this.errors['weeklyTransactions'] = err.message || 'Erreur de chargement';
+ console.error('Error loading weekly transactions:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidTransactionReport(data)) {
+ this.weeklyTransactions = data;
+ } else {
+ this.weeklyTransactions = null;
+ }
+ }),
+ finalize(() => {
+ this.loading.weeklyTransactions = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ private loadMonthlyTransactions(): Observable {
+ this.loading.monthlyTransactions = true;
+ this.errors['monthlyTransactions'] = '';
+ this.cdr.detectChanges();
+
+ return this.reportingService.getMonthlyTransactions().pipe(
+ catchError(err => {
+ this.errors['monthlyTransactions'] = err.message || 'Erreur de chargement';
+ console.error('Error loading monthly transactions:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidTransactionReport(data)) {
+ this.monthlyTransactions = data;
+ } else {
+ this.monthlyTransactions = null;
+ }
+ }),
+ finalize(() => {
+ this.loading.monthlyTransactions = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ private loadYearlyTransactions(): Observable {
+ this.loading.yearlyTransactions = true;
+ this.errors['yearlyTransactions'] = '';
+ this.cdr.detectChanges();
+
+ const startDate = `${this.currentYear}-01-01`;
+ const endDate = `${this.currentYear}-12-31`;
+
+ return this.reportingService.getTransactionsWithDates(startDate, endDate).pipe(
+ catchError(err => {
+ this.errors['yearlyTransactions'] = err.message || 'Erreur de chargement';
+ console.error('Error loading yearly transactions:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidTransactionReport(data)) {
+ this.yearlyTransactions = data;
+ } else {
+ this.yearlyTransactions = null;
+ }
+ }),
+ finalize(() => {
+ this.loading.yearlyTransactions = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ private loadTransactionsWithDates(): Observable {
+ this.loading.transactionsWithDates = true;
+ this.errors['transactionsWithDates'] = '';
+ this.cdr.detectChanges();
+
+ return this.reportingService.getTransactionsWithDates(this.startDate, this.endDate).pipe(
+ catchError(err => {
+ this.errors['transactionsWithDates'] = err.message || 'Erreur de chargement';
+ console.error('Error loading transactions with dates:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidTransactionReport(data)) {
+ this.transactionsWithDates = data;
+ } else {
+ this.transactionsWithDates = null;
+ }
+ }),
+ finalize(() => {
+ this.loading.transactionsWithDates = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ private loadDailySubscriptions(): Observable {
+ this.loading.dailySubscriptions = true;
+ this.errors['dailySubscriptions'] = '';
+ this.cdr.detectChanges();
+
+ return this.reportingService.getDailySubscriptions().pipe(
+ catchError(err => {
+ this.errors['dailySubscriptions'] = err.message || 'Erreur de chargement';
+ console.error('Error loading daily subscriptions:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidSubscriptionReport(data)) {
+ this.dailySubscriptions = data;
+ } else {
+ this.dailySubscriptions = null;
+ }
+ }),
+ finalize(() => {
+ this.loading.dailySubscriptions = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ private loadMerchantSubscriptions(): Observable {
+ this.loading.merchantSubscriptions = true;
+ this.errors['merchantSubscriptions'] = '';
+ this.cdr.detectChanges();
+
+ return this.reportingService.getMonthlySubscriptions(this.merchantId).pipe(
+ catchError(err => {
+ this.errors['merchantSubscriptions'] = err.message || 'Erreur de chargement';
+ console.error('Error loading merchant subscriptions:', err);
+ return of(null);
+ }),
+ tap(data => {
+ if (this.isValidSubscriptionReport(data)) {
+ this.merchantSubscriptions = data;
+ } else {
+ this.merchantSubscriptions = null;
+ }
+ }),
+ finalize(() => {
+ this.loading.merchantSubscriptions = false;
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ // ============ MÉTHODES PUBLIQUES ============
+ refreshMerchantData(): void {
+ this.subscriptions.push(
+ this.loadMerchantTransactions().subscribe(() => {
+ this.cdr.detectChanges();
+ }),
+ this.loadMerchantSubscriptions().subscribe(() => {
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ refreshWithDates(): void {
+ this.subscriptions.push(
+ this.loadTransactionsWithDates().subscribe(() => {
+ this.calculateStats();
+ this.prepareChartData();
+ this.updateCharts();
+ this.cdr.detectChanges();
+ })
+ );
+ }
+
+ refreshHealthCheck(): void {
+ this.checkSystemHealth();
+ }
+
+ triggerSync(): void {
+ const syncService = this.systemHealth.find(s => s.service === 'Synchronisation');
+
+ if (syncService?.status === 'Offline') {
+ this.addAlert('danger', 'Synchronisation impossible',
+ 'Le service de synchronisation est actuellement hors ligne', 'Maintenant');
+ return;
+ }
+
+ this.loading.sync = true;
+ this.syncResponse = null;
+ this.cdr.detectChanges();
+
+ const sub = this.reportingService.triggerManualSync().pipe(
+ catchError(err => {
+ this.errors['sync'] = err.message || 'Erreur de synchronisation';
+ this.addAlert('danger', 'Échec de synchronisation',
+ `La synchronisation a échoué: ${err.message}`, 'Maintenant');
+ return of(null);
+ }),
+ tap(data => {
+ if (data && typeof data.message === 'string' && typeof data.timestamp === 'string') {
+ this.syncResponse = data;
+ this.addAlert('success', 'Synchronisation réussie',
+ data.message, 'Maintenant');
+ }
+ }),
+ finalize(() => {
+ this.loading.sync = false;
+ this.cdr.detectChanges();
+ setTimeout(() => this.loadAllData(), 2000);
+ })
+ ).subscribe();
+
+ this.subscriptions.push(sub);
+ }
+
+ // ============ MÉTHODES DE RAFRAÎCHISSEMENT INDIVIDUEL ============
+ refreshDailyTransactions(): void {
+ const sub = this.loadDailyTransactions().subscribe(() => {
+ this.calculateStats();
+ this.prepareChartData();
+ this.updateCharts();
+ this.cdr.detectChanges();
+ });
+ this.subscriptions.push(sub);
+ }
+
+ refreshWeeklyTransactions(): void {
+ const sub = this.loadWeeklyTransactions().subscribe(() => {
+ this.calculateStats();
+ this.prepareChartData();
+ this.updateCharts();
+ this.cdr.detectChanges();
+ });
+ this.subscriptions.push(sub);
+ }
+
+ refreshYearlyTransactions(): void {
+ const sub = this.loadYearlyTransactions().subscribe(() => {
+ this.calculateStats();
+ this.prepareChartData();
+ this.updateCharts();
+ this.cdr.detectChanges();
+ });
+ this.subscriptions.push(sub);
+ }
+
+ refreshDailySubscriptions(): void {
+ const sub = this.loadDailySubscriptions().subscribe(() => {
+ this.calculateStats();
+ this.cdr.detectChanges();
+ });
+ this.subscriptions.push(sub);
+ }
+
+ // ============ GRAPHIQUES ============
+ private initCharts(): void {
+ if (this.successRateChartRef?.nativeElement) {
+ this.initSuccessRateChart();
+ }
+ if (this.revenueChartRef?.nativeElement) {
+ this.initRevenueChart();
+ }
+ }
+
+ private initSuccessRateChart(): void {
+ if (!this.successRateChartRef?.nativeElement) return;
+
+ if (this.successRateChart) {
+ this.successRateChart.destroy();
+ }
+
+ const successRate = this.stats.successRate;
+ const remaining = 100 - successRate;
+
+ const config: ChartConfiguration = {
+ type: 'doughnut',
+ data: {
+ datasets: [{
+ data: [successRate, remaining],
+ backgroundColor: [
+ this.getChartColor(successRate),
+ '#e9ecef'
+ ],
+ borderWidth: 0,
+ borderRadius: 10
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: {
+ legend: {
+ display: false
+ },
+ tooltip: {
+ enabled: false
+ }
+ }
+ }
+ };
+
+ this.successRateChart = new Chart(this.successRateChartRef.nativeElement, config);
+ }
+
+ private initRevenueChart(): void {
+ if (!this.revenueChartRef?.nativeElement) return;
+
+ if (this.revenueChart) {
+ this.revenueChart.destroy();
+ }
+
+ // Sélectionner les données en fonction de la période
+ let dataToShow = this.revenueChartData;
+ if (this.selectedPeriod === '30days' && this.revenueChartData.length > 30) {
+ dataToShow = this.revenueChartData.slice(-30);
+ } else if (this.selectedPeriod === '90days' && this.revenueChartData.length > 90) {
+ dataToShow = this.revenueChartData.slice(-90);
+ } else {
+ // Par défaut, 7 derniers jours
+ dataToShow = this.revenueChartData.slice(-7);
+ }
+
+ const labels = dataToShow.map(d => d.period);
+ const data = dataToShow.map(d => d.revenue);
+
+ const config: ChartConfiguration = {
+ type: 'line', // Changé de 'bar' à 'line' pour une courbe
+ data: {
+ labels: labels,
+ datasets: [{
+ label: 'Revenue (XOF)',
+ data: data,
+ backgroundColor: 'rgba(13, 110, 253, 0.1)',
+ borderColor: '#0d6efd',
+ borderWidth: 2,
+ tension: 0.4, // Courbure de la ligne
+ fill: true,
+ pointBackgroundColor: '#0d6efd',
+ pointBorderColor: '#ffffff',
+ pointBorderWidth: 2,
+ pointRadius: 4,
+ pointHoverRadius: 6
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: false
+ },
+ tooltip: {
+ callbacks: {
+ label: (context) => {
+ return `${this.formatCurrency(context.parsed.y)}`;
+ }
+ }
+ }
+ },
+ scales: {
+ y: {
+ beginAtZero: true,
+ grid: {
+ color: 'rgba(0,0,0,0.05)'
+ },
+ ticks: {
+ callback: (value) => {
+ if (typeof value === 'number') {
+ if (value >= 1000000) return `${(value / 1000000).toFixed(1)}M`;
+ if (value >= 1000) return `${(value / 1000).toFixed(0)}K`;
+ return value.toString();
+ }
+ return value;
+ }
+ }
+ },
+ x: {
+ grid: {
+ color: 'rgba(0,0,0,0.05)'
+ },
+ ticks: {
+ maxRotation: 45
+ }
+ }
+ },
+ interaction: {
+ intersect: false,
+ mode: 'index'
+ },
+ animation: {
+ duration: 1000
+ }
+ }
+ };
+
+ this.revenueChart = new Chart(this.revenueChartRef.nativeElement, config);
+ }
+
+ private updateCharts(): void {
+ this.prepareChartData();
+ this.initCharts();
+ }
+
+ onPeriodChange(): void {
+ this.initRevenueChart();
+ }
+
+ private getChartColor(rate: number): string {
+ if (rate >= 95) return '#51cf66';
+ if (rate >= 90) return '#ff922b';
+ return '#ff6b6b';
+ }
+
+ // ============ VÉRIFICATIONS DE TYPE ============
+ private isValidTransactionReport(data: any): data is TransactionReport {
+ return data !== null &&
+ data !== undefined &&
+ typeof data === 'object' &&
+ data.type === 'transaction' &&
+ typeof data.period === 'string' &&
+ typeof data.totalAmount === 'number' &&
+ typeof data.totalCount === 'number' &&
+ Array.isArray(data.items) &&
+ typeof data.summary === 'object' &&
+ typeof data.generatedAt === 'string';
+ }
+
+ private isValidSubscriptionReport(data: any): data is SubscriptionReport {
+ return data !== null &&
+ data !== undefined &&
+ typeof data === 'object' &&
+ data.type === 'subscription' &&
+ typeof data.period === 'string' &&
+ typeof data.totalAmount === 'number' &&
+ typeof data.totalCount === 'number' &&
+ Array.isArray(data.items) &&
+ typeof data.summary === 'object' &&
+ typeof data.generatedAt === 'string';
+ }
+
+ // ============ CALCUL DES STATISTIQUES ============
+ private calculateStats(): void {
+ this.stats.totalRevenue = [
+ this.dailyTransactions?.totalAmount || 0,
+ this.weeklyTransactions?.totalAmount || 0,
+ this.monthlyTransactions?.totalAmount || 0
+ ].reduce((a, b) => a + b, 0);
+
+ this.stats.totalTransactions = [
+ this.dailyTransactions?.totalCount || 0,
+ this.weeklyTransactions?.totalCount || 0,
+ this.monthlyTransactions?.totalCount || 0
+ ].reduce((a, b) => a + b, 0);
+
+ this.stats.yearlyRevenue = this.yearlyTransactions?.totalAmount || 0;
+ this.stats.yearlyTransactions = this.yearlyTransactions?.totalCount || 0;
+
+ this.stats.totalSubscriptions = [
+ this.dailySubscriptions?.totalCount || 0,
+ this.merchantSubscriptions?.totalCount || 0
+ ].reduce((a, b) => a + b, 0);
+
+ const successRates = [];
+ if (this.dailyTransactions) {
+ const rate = this.calculateSuccessRate(this.dailyTransactions);
+ if (rate > 0) successRates.push(rate);
+ }
+
+ this.stats.successRate = successRates.length > 0
+ ? successRates.reduce((a, b) => a + b, 0) / successRates.length
+ : this.stats.avgSuccessRate;
+
+ this.stats.activeSubscriptions = this.dailySubscriptions?.items?.reduce(
+ (sum, item) => sum + (item.activeCount || 0), 0
+ ) || 0;
+
+ this.stats.avgTransaction = this.stats.totalTransactions > 0
+ ? this.stats.totalRevenue / this.stats.totalTransactions
+ : 0;
+
+ if (this.dailyTransactions?.items?.length) {
+ this.stats.maxRevenueDay = Math.max(...this.dailyTransactions.items.map(item => item.totalAmount || 0));
+ }
+ }
+
+ private calculateSuccessRate(transaction: TransactionReport): number {
+ if (!transaction.items || transaction.items.length === 0) return 0;
+ const item = transaction.items[0];
+ const total = item.count || 0;
+ const success = item.successCount || 0;
+ return total > 0 ? (success / total) * 100 : 0;
+ }
+
+ private prepareChartData(): void {
+ this.revenueChartData = [];
+ if (this.dailyTransactions?.items) {
+ this.dailyTransactions.items.forEach(item => {
+ this.revenueChartData.push({
+ period: item.period,
+ revenue: item.totalAmount,
+ transactions: item.count,
+ successRate: item.count > 0 ? (item.successCount / item.count) * 100 : 0
+ });
+ });
+ }
+
+ this.successChartData = [];
+ if (this.weeklyTransactions?.items) {
+ this.weeklyTransactions.items.forEach(item => {
+ this.successChartData.push({
+ period: item.period,
+ successRate: item.count > 0 ? (item.successCount / item.count) * 100 : 0,
+ total: item.count,
+ success: item.successCount
+ });
+ });
+ }
+ }
+
+ // ============ UTILITAIRES ============
+ formatCurrency(amount: number): string {
+ if (amount >= 1000000) {
+ return `${(amount / 1000000).toFixed(1)}M XOF`;
+ } else if (amount >= 1000) {
+ return `${(amount / 1000).toFixed(0)}K XOF`;
+ }
+ return `${Math.round(amount)} XOF`;
+ }
+
+ formatNumber(num: number): string {
+ return num.toLocaleString('fr-FR');
+ }
+
+ formatDate(dateString: string): string {
+ try {
+ return new Date(dateString).toLocaleDateString('fr-FR', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ } catch {
+ return dateString;
+ }
+ }
+
+ getSuccessRate(transaction: TransactionReport | null): number {
+ if (!transaction || !transaction.items || transaction.items.length === 0) return this.stats.avgSuccessRate;
+ return this.calculateSuccessRate(transaction);
+ }
+
+ getSuccessRateClass(rate: number): string {
+ if (rate >= 95) return 'text-success';
+ if (rate >= 90) return 'text-warning';
+ return 'text-danger';
+ }
+
+ getSuccessRateBgClass(rate: number): string {
+ if (rate >= 90) return 'bg-success-subtle border-success';
+ if (rate >= 80) return 'bg-warning-subtle border-warning';
+ return 'bg-danger-subtle border-danger';
+ }
+
+ // ============ MÉTHODES POUR LES CARDS ============
+ getPaymentStats() {
+ return {
+ daily: {
+ transactions: this.dailyTransactions?.totalCount || 0,
+ revenue: this.dailyTransactions?.totalAmount || 0,
+ successRate: this.getSuccessRate(this.dailyTransactions)
+ },
+ weekly: {
+ transactions: this.weeklyTransactions?.totalCount || 0,
+ revenue: this.weeklyTransactions?.totalAmount || 0,
+ successRate: this.getSuccessRate(this.weeklyTransactions)
+ },
+ monthly: {
+ transactions: this.monthlyTransactions?.totalCount || 0,
+ revenue: this.monthlyTransactions?.totalAmount || 0,
+ successRate: this.getSuccessRate(this.monthlyTransactions)
+ },
+ yearly: {
+ transactions: this.stats.yearlyTransactions,
+ revenue: this.stats.yearlyRevenue,
+ successRate: this.stats.avgSuccessRate
+ },
+ overallSuccessRate: this.stats.successRate
+ };
+ }
+
+ getSubscriptionStats() {
+ return {
+ total: this.dailySubscriptions?.totalCount || 0,
+ active: this.stats.activeSubscriptions,
+ newToday: this.dailySubscriptions?.items?.[0]?.count || 0,
+ cancelled: this.dailySubscriptions?.items?.[0]?.cancelledCount || 0,
+ activePercentage: this.dailySubscriptions?.totalCount ?
+ (this.stats.activeSubscriptions / this.dailySubscriptions.totalCount) * 100 : 0
+ };
+ }
+
+ getPerformanceLabel(successRate: number): string {
+ if (successRate >= 95) return 'Excellent';
+ if (successRate >= 90) return 'Bon';
+ if (successRate >= 80) return 'Moyen';
+ return 'À améliorer';
+ }
+}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/payment-stats.ts b/src/app/modules/dcb-dashboard/components/payment-stats.ts
deleted file mode 100644
index 950415f..0000000
--- a/src/app/modules/dcb-dashboard/components/payment-stats.ts
+++ /dev/null
@@ -1,412 +0,0 @@
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { NgIconComponent } from '@ng-icons/core';
-import { CountUpModule } from 'ngx-countup';
-import { DcbReportingService, TransactionItem } from '../services/dcb-reporting.service';
-import { catchError, finalize } from 'rxjs/operators';
-import { of, Subscription } from 'rxjs';
-
-@Component({
- selector: 'app-payment-stats',
- imports: [CommonModule, NgIconComponent, CountUpModule],
- template: `
-
-
-
-
-
-
-
- Chargement...
-
-
Chargement des statistiques...
-
-
-
-
-
-
-
-
-
-
Journalier
-
-
- {{ dailyStats.transactions }}
-
-
-
Transactions
-
- {{ formatCurrency(dailyStats.revenue) }}
- = 95 ? 'text-success' : 'text-warning'">
-
- {{ dailyStats.successRate | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
-
-
Hebdomadaire
-
-
- {{ weeklyStats.transactions }}
-
-
-
Transactions
-
- {{ formatCurrency(weeklyStats.revenue) }}
- = 95 ? 'text-success' : 'text-warning'">
-
- {{ weeklyStats.successRate | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
-
-
Mensuel
-
-
- {{ monthlyStats.transactions }}
-
-
-
Transactions
-
- {{ formatCurrency(monthlyStats.revenue) }}
- = 95 ? 'text-success' : 'text-warning'">
-
- {{ monthlyStats.successRate | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
-
-
Taux global
-
-
- {{ overallSuccessRate | number:'1.1-1' }}
- %
-
-
Succès (30 jours)
-
- Période: 30j
- = 95 ? 'text-success' : overallSuccessRate >= 90 ? 'text-warning' : 'text-danger'">
- {{ getPerformanceLabel(overallSuccessRate) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Performance globale:
- = 95 ? 'text-success' : overallSuccessRate >= 90 ? 'text-warning' : 'text-danger'">
- {{ overallSuccessRate | number:'1.1-1' }}% de taux de succès
-
-
- ({{ dailyStats.transactions }} transactions aujourd'hui)
-
-
-
-
- Mise à jour: {{ lastUpdated | date:'HH:mm' }}
-
-
-
-
-
-
-
- `,
- styles: [`
- .spin {
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- `]
-})
-export class PaymentStats implements OnInit, OnDestroy {
- loading = false;
- error: string | null = null;
-
- // Statistiques calculées à partir des données brutes
- dailyStats = { transactions: 0, revenue: 0, successRate: 0 };
- weeklyStats = { transactions: 0, revenue: 0, successRate: 0 };
- monthlyStats = { transactions: 0, revenue: 0, successRate: 0 };
- overallSuccessRate = 0;
- lastUpdated = new Date();
-
- // Données brutes de l'API
- private dailyData: TransactionItem[] = [];
- private weeklyData: TransactionItem[] = [];
- private monthlyData: TransactionItem[] = [];
-
- private dailySubscription?: Subscription;
- private weeklySubscription?: Subscription;
- private monthlySubscription?: Subscription;
-
- constructor(private reportingService: DcbReportingService) {}
-
- ngOnInit() {
- this.loadStats();
- }
-
- ngOnDestroy() {
- this.cleanupSubscriptions();
- }
-
- private cleanupSubscriptions() {
- if (this.dailySubscription) {
- this.dailySubscription.unsubscribe();
- }
- if (this.weeklySubscription) {
- this.weeklySubscription.unsubscribe();
- }
- if (this.monthlySubscription) {
- this.monthlySubscription.unsubscribe();
- }
- }
-
- loadStats() {
- console.log('PaymentStats - Starting loadStats()');
-
- this.cleanupSubscriptions(); // Nettoyer les anciennes souscriptions
-
- this.loading = true;
- this.error = null;
-
- const today = new Date();
- const threeDaysAgo = new Date(today);
- threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
-
- const twoWeeksAgo = new Date(today);
- twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14);
-
- const threeMonthsAgo = new Date(today);
- threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
-
- console.log('PaymentStats - Date ranges:', {
- threeDaysAgo: this.reportingService.formatDate(threeDaysAgo),
- today: this.reportingService.formatDate(today),
- twoWeeksAgo: this.reportingService.formatDate(twoWeeksAgo),
- threeMonthsAgo: this.reportingService.formatDate(threeMonthsAgo)
- });
-
- // Charger les données séquentiellement
- this.loadSequentialData(threeDaysAgo, twoWeeksAgo, threeMonthsAgo, today);
- }
-
- loadSequentialData(threeDaysAgo: Date, twoWeeksAgo: Date, threeMonthsAgo: Date, today: Date) {
- // 1. Charger les données quotidiennes
- this.dailySubscription = this.reportingService.getDailyTransactions(
- this.reportingService.formatDate(threeDaysAgo),
- this.reportingService.formatDate(today)
- )
- .pipe(
- catchError(err => {
- console.error('PaymentStats - Error loading daily data:', err);
- return of([]);
- })
- )
- .subscribe({
- next: (dailyData) => {
- console.log('PaymentStats - Daily data loaded:', dailyData.length);
- this.dailyData = dailyData;
-
- // 2. Après les données quotidiennes, charger les données hebdomadaires
- this.weeklySubscription = this.reportingService.getWeeklyTransactions(
- this.reportingService.formatDate(twoWeeksAgo),
- this.reportingService.formatDate(today)
- )
- .pipe(
- catchError(err => {
- console.error('PaymentStats - Error loading weekly data:', err);
- return of([]);
- })
- )
- .subscribe({
- next: (weeklyData) => {
- console.log('PaymentStats - Weekly data loaded:', weeklyData.length);
- this.weeklyData = weeklyData;
-
- // 3. Après les données hebdomadaires, charger les données mensuelles
- this.monthlySubscription = this.reportingService.getMonthlyTransactions(
- this.reportingService.formatDate(threeMonthsAgo),
- this.reportingService.formatDate(today)
- )
- .pipe(
- catchError(err => {
- console.error('PaymentStats - Error loading monthly data:', err);
- return of([]);
- }),
- finalize(() => {
- console.log('PaymentStats - All data loaded, processing...');
- this.loading = false;
- this.lastUpdated = new Date();
- this.processAllData();
- })
- )
- .subscribe({
- next: (monthlyData) => {
- console.log('PaymentStats - Monthly data loaded:', monthlyData.length);
- this.monthlyData = monthlyData;
- },
- error: (error) => {
- console.error('PaymentStats - Monthly subscription error:', error);
- }
- });
- },
- error: (error) => {
- console.error('PaymentStats - Weekly subscription error:', error);
- }
- });
- },
- error: (error) => {
- console.error('PaymentStats - Daily subscription error:', error);
- }
- });
- }
-
- processAllData() {
- console.log('PaymentStats - Processing all data:', {
- daily: this.dailyData.length,
- weekly: this.weeklyData.length,
- monthly: this.monthlyData.length
- });
-
- // Vérifier si nous avons des données
- const hasAnyData = this.dailyData.length > 0 ||
- this.weeklyData.length > 0 ||
- this.monthlyData.length > 0;
-
- if (!hasAnyData) {
- console.warn('PaymentStats - No data available');
- return;
- }
-
- // Calculer les statistiques avec les données disponibles
- this.calculateStatsFromRawData();
- }
-
- calculateStatsFromRawData() {
- // Calculer les statistiques pour chaque période à partir des données brutes
- this.dailyStats = this.calculatePeriodStats(this.dailyData);
- this.weeklyStats = this.calculatePeriodStats(this.weeklyData);
- this.monthlyStats = this.calculatePeriodStats(this.monthlyData);
-
- // Utiliser le taux de succès mensuel comme taux global
- this.overallSuccessRate = this.monthlyStats.successRate;
-
- // Si aucune donnée mensuelle, utiliser la meilleure période disponible
- if (this.monthlyStats.transactions === 0) {
- if (this.weeklyStats.transactions > 0) {
- this.overallSuccessRate = this.weeklyStats.successRate;
- } else if (this.dailyStats.transactions > 0) {
- this.overallSuccessRate = this.dailyStats.successRate;
- }
- }
-
- console.log('PaymentStats - Calculated stats from raw data:', {
- daily: this.dailyStats,
- weekly: this.weeklyStats,
- monthly: this.monthlyStats,
- overallSuccess: this.overallSuccessRate
- });
-
- // Si toutes les données sont vides, afficher un message
- if (this.dailyStats.transactions === 0 &&
- this.weeklyStats.transactions === 0 &&
- this.monthlyStats.transactions === 0) {
- this.error = 'Les données sont actuellement vides.';
- }
- }
-
- calculatePeriodStats(data: TransactionItem[]): { transactions: number; revenue: number; successRate: number } {
- if (!data || data.length === 0) {
- return { transactions: 0, revenue: 0, successRate: 0 };
- }
-
- // Calculer les totaux sur tous les éléments
- const totalTransactions = data.reduce((sum, item) => sum + (item.count || 0), 0);
- const totalRevenue = data.reduce((sum, item) => sum + (item.totalAmount || 0), 0);
- const totalSuccessful = data.reduce((sum, item) => sum + (item.successCount || 0), 0);
-
- const successRate = totalTransactions > 0 ? (totalSuccessful / totalTransactions) * 100 : 0;
-
- console.log('PaymentStats - Period stats calculation:', {
- dataLength: data.length,
- totalTransactions,
- totalRevenue,
- totalSuccessful,
- successRate
- });
-
- return {
- transactions: totalTransactions,
- revenue: totalRevenue,
- successRate
- };
- }
-
- getPerformanceLabel(successRate: number): string {
- if (successRate >= 95) return 'Excellent';
- if (successRate >= 90) return 'Bon';
- if (successRate >= 80) return 'Moyen';
- if (successRate >= 70) return 'Passable';
- return 'À améliorer';
- }
-
- formatCurrency(amount: number): string {
- if (amount >= 1000000) {
- return `${(amount / 1000000).toFixed(1)}M XOF`;
- } else if (amount >= 1000) {
- return `${(amount / 1000).toFixed(0)}K XOF`;
- }
- return `${Math.round(amount)} XOF`;
- }
-
- refresh() {
- console.log('PaymentStats - Refreshing data');
-
- // Essayer de vider le cache si disponible
- if (this.reportingService.clearCache) {
- this.reportingService.clearCache();
- }
-
- this.loadStats();
- }
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/recent-transactions.ts b/src/app/modules/dcb-dashboard/components/recent-transactions.ts
deleted file mode 100644
index 2ed4746..0000000
--- a/src/app/modules/dcb-dashboard/components/recent-transactions.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { SubscriptionsService } from '../../subscriptions/subscriptions.service';
-import { Subscription} from '@core/models/dcb-bo-hub-subscription.model';
-import { catchError, finalize } from 'rxjs/operators';
-import { of } from 'rxjs';
-
-@Component({
- selector: 'app-recent-transactions',
- imports: [CommonModule],
- template: `
-
-
-
-
-
-
-
- Chargement...
-
-
Chargement des abonnements...
-
-
-
-
-
-
{{ error }}
-
-
-
-
-
-
-
-
- | ID |
- Date de création |
- Statut |
- Montant |
- Périodicité |
- Client |
-
-
-
-
- |
- #{{ subscription.id }}
- {{ subscription.externalReference || 'N/A' }}
- |
-
- {{ subscription.createdAt | date:'dd/MM/yyyy' }}
- {{ subscription.createdAt | date:'HH:mm' }}
- |
-
-
-
- {{ getStatusLabel(subscription.status) }}
-
- |
-
- {{ subscription.amount | currency:subscription.currency:'symbol':'1.0-0' }}
- {{ subscription.currency }}
- |
-
- {{ getPeriodicityLabel(subscription.periodicity) }}
-
- Prochain: {{ subscription.nextPaymentDate | date:'dd/MM' }}
-
- |
-
- Client #{{ subscription.customerId }}
- Partenaire: {{ subscription.merchantPartnerId }}
- |
-
-
-
-
-
-
- Aucun abonnement trouvé
-
- |
-
-
-
-
-
-
-
0" class="card-footer">
-
-
- Affichage de {{ subscriptions.length }} abonnements récents
-
-
-
Total: {{ statistics?.total || 0 }} abonnements
-
-
- {{ statistics?.active || 0 }} actifs
-
-
- {{ statistics?.suspended || 0 }} suspendus
-
-
-
-
-
-
-
- `,
- styles: [``]
-})
-export class RecentTransactions implements OnInit {
- loading = false;
- error: string | null = null;
-
- subscriptions: Subscription[] = [];
- statistics: any = null;
-
- constructor(private subscriptionsService: SubscriptionsService) {}
-
- ngOnInit() {
- this.loadRecentSubscriptions();
- }
-
- loadRecentSubscriptions() {
- this.loading = true;
- this.error = null;
-
- this.subscriptionsService.getRecentSubscriptions(10)
- .pipe(
- catchError(err => {
- console.error('Erreur lors du chargement des abonnements:', err);
- this.error = 'Impossible de charger les abonnements. Veuillez réessayer.';
- return of({
- subscriptions: [],
- statistics: {
- total: 0,
- active: 0,
- suspended: 0,
- cancelled: 0,
- totalRevenue: 0,
- averageAmount: 0
- }
- });
- }),
- finalize(() => {
- this.loading = false;
- })
- )
- .subscribe({
- next: (response) => {
- this.subscriptions = response.subscriptions;
- }
- });
- }
-
- getStatusClass(status: string): string {
- switch(status?.toUpperCase()) {
- case 'ACTIVE':
- return 'bg-success-subtle text-success border border-success-subtle';
- case 'PENDING':
- return 'bg-warning-subtle text-warning border border-warning-subtle';
- case 'SUSPENDED':
- return 'bg-secondary-subtle text-secondary border border-secondary-subtle';
- case 'CANCELLED':
- return 'bg-danger-subtle text-danger border border-danger-subtle';
- case 'FAILED':
- return 'bg-danger-subtle text-danger border border-danger-subtle';
- case 'EXPIRED':
- return 'bg-info-subtle text-info border border-info-subtle';
- default:
- return 'bg-light text-dark border';
- }
- }
-
- getStatusIcon(status: string): string {
- switch(status?.toUpperCase()) {
- case 'ACTIVE':
- return 'lucideCheckCircle';
- case 'PENDING':
- return 'lucideClock';
- case 'SUSPENDED':
- return 'lucidePauseCircle';
- case 'CANCELLED':
- return 'lucideXCircle';
- case 'FAILED':
- return 'lucideAlertCircle';
- case 'EXPIRED':
- return 'lucideCalendarX';
- default:
- return 'lucideHelpCircle';
- }
- }
-
- getStatusLabel(status: string): string {
- switch(status?.toUpperCase()) {
- case 'ACTIVE':
- return 'Actif';
- case 'PENDING':
- return 'En attente';
- case 'SUSPENDED':
- return 'Suspendu';
- case 'CANCELLED':
- return 'Annulé';
- case 'FAILED':
- return 'Échoué';
- case 'EXPIRED':
- return 'Expiré';
- default:
- return status || 'Inconnu';
- }
- }
-
- getPeriodicityLabel(periodicity: string): string {
- switch(periodicity?.toUpperCase()) {
- case 'DAILY':
- return 'Quotidien';
- case 'WEEKLY':
- return 'Hebdomadaire';
- case 'MONTHLY':
- return 'Mensuel';
- case 'YEARLY':
- return 'Annuel';
- default:
- return periodicity || 'Non défini';
- }
- }
-
- refresh() {
- this.loadRecentSubscriptions();
- }
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/subscription-overview.ts b/src/app/modules/dcb-dashboard/components/subscription-overview.ts
deleted file mode 100644
index 5d04a35..0000000
--- a/src/app/modules/dcb-dashboard/components/subscription-overview.ts
+++ /dev/null
@@ -1,307 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { Chartjs } from '@app/components/chartjs';
-import { ChartConfiguration } from 'chart.js';
-import { getColor } from '@/app/utils/color-utils';
-import { DcbReportingService, SubscriptionItem } from '../services/dcb-reporting.service';
-import { catchError, finalize } from 'rxjs/operators';
-import { of } from 'rxjs';
-
-@Component({
- selector: 'app-subscription-overview',
- imports: [CommonModule, Chartjs],
- template: `
-
-
-
-
-
-
-
- Chargement...
-
-
Chargement des données...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Actifs
-
- {{ formatNumber(activeSubscriptions) }}
- 0">
-
- +{{ growthRate | number:'1.1-1' }}%
-
-
-
- {{ growthRate | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
-
En période d'essai
-
- {{ formatNumber(trialSubscriptions) }}
- {{ trialPercentage | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
Annulés
-
- {{ formatNumber(cancelledSubscriptions) }}
- {{ cancelledPercentage | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
-
Expirés
-
- {{ formatNumber(expiredSubscriptions) }}
- {{ expiredPercentage | number:'1.1-1' }}%
-
-
-
-
-
-
-
-
-
Nouveaux abonnements (30 derniers jours)
-
-
-
- {{ formatNumber(newSubscriptions) }}
- nouveaux
-
-
-
- Dernière mise à jour: {{ lastUpdated | date:'HH:mm' }}
-
-
-
-
-
-
- `,
- styles: [`
- .spin {
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- `]
-})
-export class SubscriptionOverview implements OnInit {
- loading = false;
- error: string | null = null;
- lastUpdated = new Date();
-
- // Données d'abonnements
- totalSubscriptions = 0;
- activeSubscriptions = 0;
- trialSubscriptions = 0;
- cancelledSubscriptions = 0;
- expiredSubscriptions = 0;
- newSubscriptions = 0;
-
- // Pourcentages
- trialPercentage = 0;
- cancelledPercentage = 0;
- expiredPercentage = 0;
- newSubscriptionsPercentage = 0;
- growthRate = 0;
-
- constructor(private reportingService: DcbReportingService) {}
-
- ngOnInit() {
- this.loadSubscriptionData();
- }
-
- loadSubscriptionData() {
- this.loading = true;
- this.error = null;
-
- // Récupérer les 30 derniers jours
- const endDate = new Date();
- const startDate = new Date();
- startDate.setDate(startDate.getDate() - 30);
-
- const startDateStr = this.formatDate(startDate);
- const endDateStr = this.formatDate(endDate);
-
- console.log('SubscriptionOverview - Loading data for period:', startDateStr, 'to', endDateStr);
-
- this.reportingService.getDailySubscriptions(startDateStr, endDateStr)
- .pipe(
- catchError(err => {
- console.error('SubscriptionOverview - API error:', err);
- this.error = 'Impossible de charger les données des abonnements';
- return of([]); // Retourner un tableau vide
- }),
- finalize(() => {
- this.loading = false;
- this.lastUpdated = new Date();
- })
- )
- .subscribe({
- next: (subscriptions: SubscriptionItem[]) => {
- console.log('SubscriptionOverview - Received data:', subscriptions);
- this.processSubscriptionData(subscriptions);
- }
- });
- }
-
- processSubscriptionData(subscriptions: SubscriptionItem[]) {
- if (!subscriptions || subscriptions.length === 0) {
- console.warn('SubscriptionOverview - No subscription data');
- return; // Afficher rien
- }
-
- // Utiliser la dernière période disponible
- const latestData = subscriptions[subscriptions.length - 1];
-
- // Note: Votre API retourne activeCount et cancelledCount, pas trial/expired/new
- this.activeSubscriptions = latestData.activeCount || 0;
- this.cancelledSubscriptions = latestData.cancelledCount || 0;
-
- // Pour les champs manquants, mettre à 0 ou estimer selon votre logique métier
- this.trialSubscriptions = 0;
- this.expiredSubscriptions = 0;
- this.newSubscriptions = 0;
-
- this.totalSubscriptions = latestData.count || 0;
- }
-
- subscriptionChart = (): ChartConfiguration => ({
- type: 'doughnut',
- data: {
- labels: ['Actifs', 'En essai', 'Annulés', 'Expirés'],
- datasets: [{
- data: [
- this.activeSubscriptions,
- this.trialSubscriptions,
- this.cancelledSubscriptions,
- this.expiredSubscriptions
- ],
- backgroundColor: [
- getColor('chart-success'),
- getColor('chart-warning'),
- getColor('chart-danger'),
- getColor('chart-info')
- ],
- borderWidth: 2,
- borderColor: '#fff'
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: {
- display: false
- },
- tooltip: {
- callbacks: {
- label: (context) => {
- const label = context.label || '';
- const value = context.raw as number;
- const total = this.totalSubscriptions;
- const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
- return `${label}: ${this.formatNumber(value)} (${percentage}%)`;
- }
- }
- }
- }
- }
- });
-
- formatNumber(value: number): string {
- if (value >= 1000000) {
- return `${(value / 1000000).toFixed(1)}M`;
- } else if (value >= 1000) {
- return `${(value / 1000).toFixed(1)}K`;
- }
- return value.toLocaleString('fr-FR');
- }
-
- refresh() {
- this.reportingService.clearCache?.();
- this.loadSubscriptionData();
- }
-
- private formatDate(date: Date): string {
- return date.toISOString().split('T')[0];
- }
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/components/success-rate-chart.ts b/src/app/modules/dcb-dashboard/components/success-rate-chart.ts
deleted file mode 100644
index 57e0c8c..0000000
--- a/src/app/modules/dcb-dashboard/components/success-rate-chart.ts
+++ /dev/null
@@ -1,281 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { Chartjs } from '@app/components/chartjs';
-import { ChartConfiguration } from 'chart.js';
-import { getColor } from '@/app/utils/color-utils';
-import { DcbReportingService, TransactionItem } from '../services/dcb-reporting.service';
-import { CommonModule } from '@angular/common';
-import { catchError, finalize } from 'rxjs/operators';
-
-@Component({
- selector: 'app-success-rate-chart',
- imports: [Chartjs, CommonModule],
- template: `
-
-
-
-
-
-
-
-
-
-
-
{{ overallSuccessRate | number:'1.1-1' }}%
- Taux global de succès
-
-
-
-
-
-
-
-
-
-
-
-
-
Transactions réussies
-
{{ formatNumber(successfulTransactions) }}
-
-
-
-
-
-
-
-
-
Transactions échouées
-
{{ formatNumber(failedTransactions) }}
-
-
-
-
-
-
-
-
-
En attente
-
{{ formatNumber(pendingTransactions) }}
-
-
-
-
-
-
-
-
-
Total transactions
-
{{ formatNumber(totalTransactions) }}
-
-
-
-
-
-
-
- `,
-})
-export class SuccessRateChart implements OnInit {
- loading = false;
- error: string | null = null;
-
- // Données du graphique
- chartLabels: string[] = [];
- successRates: number[] = [];
-
- // Statistiques
- overallSuccessRate = 0;
- successfulTransactions = 0;
- failedTransactions = 0;
- pendingTransactions = 0;
- totalTransactions = 0;
-
- constructor(private reportingService: DcbReportingService) {}
-
- ngOnInit() {
- this.loadChartData();
- }
-
- loadChartData() {
- this.loading = true;
- this.error = null;
-
- // Récupérer les 30 derniers jours
- const endDate = new Date();
- const startDate = new Date();
- startDate.setDate(startDate.getDate() - 30);
-
- const startDateStr = this.formatDate(startDate);
- const endDateStr = this.formatDate(endDate);
-
- this.reportingService.getDailyTransactions(startDateStr, endDateStr)
- .pipe(
- catchError(err => {
- console.error('Erreur API:', err);
- this.error = 'Données non disponibles';
- return [];
- }),
- finalize(() => {
- this.loading = false;
- })
- )
- .subscribe({
- next: (transactions: TransactionItem[]) => {
- this.processTransactionData(transactions);
- }
- });
- }
-
- processTransactionData(transactions: any[]) {
- // Calculer les statistiques globales
- let totalSuccess = 0;
- let totalFailed = 0;
- let totalPending = 0;
- let totalAll = 0;
-
- transactions.forEach(transaction => {
- totalSuccess += transaction.successful || 0;
- totalFailed += transaction.failed || 0;
- totalPending += transaction.pending || 0;
- totalAll += transaction.total || 0;
- });
-
- this.successfulTransactions = totalSuccess;
- this.failedTransactions = totalFailed;
- this.pendingTransactions = totalPending;
- this.totalTransactions = totalAll;
-
- // Calculer le taux de succès global
- if (totalAll > 0) {
- this.overallSuccessRate = (totalSuccess / totalAll) * 100;
- }
-
- // Préparer les données pour le graphique (dernières 7 jours)
- const last7Days = transactions.slice(-7);
- this.chartLabels = last7Days.map(t => {
- const date = new Date(t.date);
- return date.toLocaleDateString('fr-FR', { weekday: 'short', day: 'numeric' });
- });
-
- this.successRates = last7Days.map(t => {
- const total = t.total || 0;
- const success = t.successful || 0;
- return total > 0 ? (success / total) * 100 : 0;
- });
-
- }
-
- successRateChart = (): ChartConfiguration => ({
- type: 'line',
- data: {
- labels: this.chartLabels,
- datasets: [
- {
- label: 'Taux de succès (%)',
- data: this.successRates,
- borderColor: getColor('chart-success'),
- backgroundColor: getColor('chart-success') + '20',
- borderWidth: 2,
- fill: true,
- tension: 0.4,
- pointBackgroundColor: getColor('chart-success'),
- pointBorderColor: '#fff',
- pointBorderWidth: 2,
- pointRadius: 4,
- pointHoverRadius: 6
- }
- ]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: {
- display: false
- },
- tooltip: {
- mode: 'index',
- intersect: false,
- callbacks: {
- label: (context) => {
- return `Taux de succès: ${context.raw}%`;
- }
- }
- }
- },
- scales: {
- x: {
- grid: {
- display: false
- },
- ticks: {
- color: '#6c757d'
- }
- },
- y: {
- beginAtZero: false,
- min: 80,
- max: 100,
- grid: {
- tickBorderDash: [5, 5]
- },
- ticks: {
- callback: (value) => `${value}%`,
- color: '#6c757d'
- },
- title: {
- display: true,
- text: 'Taux de succès (%)'
- }
- }
- }
- }
- });
-
- formatNumber(value: number): string {
- if (value >= 1000000) {
- return `${(value / 1000000).toFixed(1)}M`;
- } else if (value >= 1000) {
- return `${(value / 1000).toFixed(0)}K`;
- }
- return value.toString();
- }
-
- private formatDate(date: Date): string {
- return date.toISOString().split('T')[0];
- }
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/dcb-dashboard.html b/src/app/modules/dcb-dashboard/dcb-dashboard.html
index fee26bc..cecea17 100644
--- a/src/app/modules/dcb-dashboard/dcb-dashboard.html
+++ b/src/app/modules/dcb-dashboard/dcb-dashboard.html
@@ -7,7 +7,7 @@
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/dcb-dashboard.spec.ts b/src/app/modules/dcb-dashboard/dcb-dashboard.spec.ts
index 8bd3987..ba834c8 100644
--- a/src/app/modules/dcb-dashboard/dcb-dashboard.spec.ts
+++ b/src/app/modules/dcb-dashboard/dcb-dashboard.spec.ts
@@ -1,17 +1,17 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
-import { Dashboard } from './dcb-dashboard'
+import { DcbDashboard } from './dcb-dashboard'
describe('Dashboard', () => {
- let component: Dashboard
- let fixture: ComponentFixture
+ let component: DcbDashboard
+ let fixture: ComponentFixture
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [Dashboard],
+ imports: [DcbDashboard],
}).compileComponents()
- fixture = TestBed.createComponent(Dashboard)
+ fixture = TestBed.createComponent(DcbDashboard)
component = fixture.componentInstance
fixture.detectChanges()
})
diff --git a/src/app/modules/dcb-dashboard/dcb-dashboard.ts b/src/app/modules/dcb-dashboard/dcb-dashboard.ts
index ad7e254..6a75cb4 100644
--- a/src/app/modules/dcb-dashboard/dcb-dashboard.ts
+++ b/src/app/modules/dcb-dashboard/dcb-dashboard.ts
@@ -1,14 +1,14 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageTitle } from '@app/components/page-title/page-title';
-import { DashboardReport } from './components/dcb-dashboard-report';
+import { DcbReportingDashboard } from './components/dcb-reporting-dashboard';
@Component({
selector: 'app-dcb-dashboard',
imports: [
CommonModule,
PageTitle,
- DashboardReport,
+ DcbReportingDashboard,
],
templateUrl: './dcb-dashboard.html',
})
diff --git a/src/app/modules/dcb-dashboard/models/dcb-dashboard.models.ts b/src/app/modules/dcb-dashboard/models/dcb-dashboard.models.ts
deleted file mode 100644
index 7261249..0000000
--- a/src/app/modules/dcb-dashboard/models/dcb-dashboard.models.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-export interface KpiCardModel {
- title: string;
- value: number | string;
- change?: number;
- changeType?: 'positive' | 'negative' | 'neutral';
- icon: string;
- color: string;
- format?: 'currency' | 'number' | 'percentage';
- currency?: string;
-}
-
-export interface PaymentChartData {
- period: string;
- successful: number;
- failed: number;
- pending: number;
- revenue: number;
-}
-
-export interface SubscriptionStatsModel {
- total: number;
- active: number;
- trial: number;
- cancelled: number;
- expired: number;
- growthRate: number;
-}
-
-export interface Alert {
- id: string;
- type: 'error' | 'warning' | 'info' | 'success';
- title: string;
- message: string;
- timestamp: Date;
- acknowledged: boolean;
- priority: 'low' | 'medium' | 'high';
- action?: {
- label: string;
- route?: string;
- action?: () => void;
- };
-}
-
-export interface DcbDashboardData {
- kpis: KpiCardModel[];
- paymentChart: PaymentChartData[];
- subscriptionStats: SubscriptionStatsModel;
- alerts: Alert[];
- lastUpdated: Date;
-}
-
-// Interface pour la réponse brute de l'API
-export interface ApiTransactionItem {
- period: string;
- totalAmount: number;
- totalTax: number;
- count: number;
- successCount: number;
- failedCount: number;
- pendingCount: number;
- merchantPartnerId?: number;
-}
-
-export interface ApiTransactionResponse {
- type: string;
- period: string;
- startDate: string;
- endDate: string;
- merchantPartnerId?: number;
- totalAmount: number;
- totalCount: number;
- items: ApiTransactionItem[];
- summary: {
- avgAmount: number;
- minAmount: number;
- maxAmount: number;
- };
- generatedAt: string;
-}
-
-export interface ApiSubscriptionItem {
- period: string;
- active: number;
- trial: number;
- cancelled: number;
- expired: number;
- new: number;
- total: number;
- merchantPartnerId?: number;
-}
-
-export interface ApiSubscriptionResponse {
- type: string;
- period: string;
- startDate: string;
- endDate: string;
- merchantPartnerId?: number;
- totalCount: number;
- items: ApiSubscriptionItem[];
- summary: {
- avgActive: number;
- avgNew: number;
- };
- generatedAt: string;
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/models/dcb-reporting.models.ts b/src/app/modules/dcb-dashboard/models/dcb-reporting.models.ts
new file mode 100644
index 0000000..e405d3e
--- /dev/null
+++ b/src/app/modules/dcb-dashboard/models/dcb-reporting.models.ts
@@ -0,0 +1,68 @@
+export interface TransactionReport {
+ type: 'transaction';
+ period: 'daily' | 'weekly' | 'monthly';
+ startDate: string;
+ endDate: string;
+ merchantPartnerId?: number;
+ totalAmount: number;
+ totalCount: number;
+ items: TransactionItem[];
+ summary: TransactionSummary;
+ generatedAt: string;
+}
+
+export interface TransactionItem {
+ period: string;
+ totalAmount: number;
+ totalTax: number;
+ count: number;
+ successCount: number;
+ failedCount: number;
+ pendingCount: number;
+ merchantPartnerId?: number;
+}
+
+export interface TransactionSummary {
+ avgAmount: number;
+ minAmount: number;
+ maxAmount: number;
+}
+
+export interface SubscriptionReport {
+ type: 'subscription';
+ period: 'daily' | 'weekly' | 'monthly';
+ startDate: string;
+ endDate: string;
+ merchantPartnerId?: number;
+ totalAmount: number;
+ totalCount: number;
+ items: SubscriptionItem[];
+ summary: SubscriptionSummary;
+ generatedAt: string;
+}
+
+export interface SubscriptionItem {
+ period: string;
+ totalAmount: number;
+ count: number;
+ activeCount: number;
+ cancelledCount: number;
+ merchantPartnerId?: number;
+}
+
+export interface SubscriptionSummary {
+ avgAmount: number;
+ minAmount: number;
+ maxAmount: number;
+}
+
+export interface SyncResponse {
+ message: string;
+ timestamp: string;
+}
+
+export interface ReportParams {
+ startDate?: string;
+ endDate?: string;
+ merchantPartnerId?: number;
+}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/services/dcb-dashboard.service.ts b/src/app/modules/dcb-dashboard/services/dcb-dashboard.service.ts
deleted file mode 100644
index d81ce9a..0000000
--- a/src/app/modules/dcb-dashboard/services/dcb-dashboard.service.ts
+++ /dev/null
@@ -1,459 +0,0 @@
-import { Injectable, inject } from '@angular/core';
-import { HttpClient, HttpParams } from '@angular/common/http';
-import { Observable, of, forkJoin } from 'rxjs';
-import { map, catchError, shareReplay } from 'rxjs/operators';
-import {
- DcbDashboardData,
- KpiCardModel,
- PaymentChartData,
- SubscriptionStatsModel,
- Alert
-} from '../models/dcb-dashboard.models';
-import { environment } from '@environments/environment';
-
-export interface TransactionReport {
- date: string;
- successful: number;
- failed: number;
- pending: number;
- revenue: number;
- total: number;
-}
-
-export interface SubscriptionReport {
- date: string;
- active: number;
- trial: number;
- cancelled: number;
- expired: number;
- new: number;
- total: number;
-}
-
-@Injectable({ providedIn: 'root' })
-export class DcbDashboardService {
- private http = inject(HttpClient);
- private reportingApiUrl = `${environment.reportingApiUrl}/reporting`;
-
- private cache = new Map>();
-
- // Données mockées pour fallback
- private mockData: DcbDashboardData = {
- kpis: [
- {
- title: 'Revenue Mensuel',
- value: 0,
- change: 0,
- changeType: 'positive',
- icon: 'lucideTrendingUp',
- color: 'success',
- format: 'currency',
- currency: 'XOF'
- },
- {
- title: 'Transactions Journalières',
- value: 0,
- change: 0,
- changeType: 'positive',
- icon: 'lucideCreditCard',
- color: 'primary',
- format: 'number'
- },
- {
- title: 'Taux de Succès',
- value: 0,
- change: 0,
- changeType: 'positive',
- icon: 'lucideCheckCircle',
- color: 'info',
- format: 'percentage'
- },
- {
- title: 'Nouveaux Abonnements',
- value: 0,
- change: 0,
- changeType: 'positive',
- icon: 'lucideUsers',
- color: 'warning',
- format: 'number'
- }
- ],
- paymentChart: [],
- subscriptionStats: {
- total: 0,
- active: 0,
- trial: 0,
- cancelled: 0,
- expired: 0,
- growthRate: 0
- },
- alerts: [],
- lastUpdated: new Date()
- };
-
- /**
- * Récupère toutes les données du dashboard
- */
- getDcbDashboardData(): Observable {
- const cacheKey = 'dashboard-full-data';
-
- if (!this.cache.has(cacheKey)) {
- const dashboardData$ = this.fetchAllDashboardData().pipe(
- shareReplay({ bufferSize: 1, refCount: true }),
- catchError(error => {
- console.error('Erreur dashboard:', error);
- return of(this.mockData);
- })
- );
-
- this.cache.set(cacheKey, dashboardData$);
- }
-
- return this.cache.get(cacheKey)!;
- }
-
- /**
- * Récupère toutes les données nécessaires
- */
- private fetchAllDashboardData(): Observable {
- /*if (!environment.production) {
- return of(this.mockData);
- }*/
-
- // Calcul des dates (30 derniers jours)
- const endDate = new Date();
- const startDate = new Date();
- startDate.setDate(startDate.getDate() - 30);
-
- const startDateStr = this.formatDate(startDate);
- const endDateStr = this.formatDate(endDate);
-
- // Récupération des transactions mensuelles pour les KPIs
- return this.getMonthlyTransactions(startDateStr, endDateStr).pipe(
- map(monthlyData => this.transformDashboardData(monthlyData))
- );
- }
-
- /**
- * Récupère les transactions journalières (pour le graphique)
- */
- getDailyTransactions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/transactions/daily`;
- return this.http.get(url, { params }).pipe(
- map(response => response.data || []),
- catchError(() => of([]))
- );
- }
-
- /**
- * Récupère les transactions mensuelles (pour les KPIs)
- */
- getMonthlyTransactions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/transactions/monthly`;
- return this.http.get(url, { params }).pipe(
- map(response => response.data || []),
- catchError(() => of([]))
- );
- }
-
- /**
- * Récupère les abonnements journaliers
- */
- getDailySubscriptions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/subscriptions/daily`;
- return this.http.get(url, { params }).pipe(
- map(response => response.data || []),
- catchError(() => of([]))
- );
- }
-
- /**
- * Récupère les abonnements mensuels
- */
- getMonthlySubscriptions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/subscriptions/monthly`;
- return this.http.get(url, { params }).pipe(
- map(response => response.data || []),
- catchError(() => of([]))
- );
- }
-
- /**
- * Lance une synchronisation manuelle
- */
- triggerManualSync(): Observable<{ success: boolean; message?: string }> {
- const url = `${this.reportingApiUrl}/sync/full`;
- return this.http.post<{ success: boolean; message?: string }>(url, {}).pipe(
- catchError(error => of({
- success: false,
- message: error.message || 'Erreur lors de la synchronisation'
- }))
- );
- }
-
- /**
- * Transforme les données d'API en format dashboard
- */
- private transformDashboardData(monthlyTransactions: TransactionReport[]): DcbDashboardData {
- // Calcul des KPIs à partir des transactions mensuelles
- const currentMonth = this.getCurrentMonthData(monthlyTransactions);
- const previousMonth = this.getPreviousMonthData(monthlyTransactions);
-
- const totalRevenue = this.calculateTotalRevenue(monthlyTransactions);
- const revenueChange = this.calculateRevenueChange(currentMonth, previousMonth);
-
- const totalTransactions = this.calculateTotalTransactions(monthlyTransactions);
- const transactionChange = this.calculateTransactionChange(currentMonth, previousMonth);
-
- const successRate = this.calculateSuccessRate(monthlyTransactions);
- const successRateChange = this.calculateSuccessRateChange(currentMonth, previousMonth);
-
- // Récupération des abonnements pour le 4ème KPI
- const today = this.formatDate(new Date());
- const lastMonthDate = this.formatDate(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000));
-
- return {
- kpis: [
- {
- title: 'Revenue Mensuel',
- value: totalRevenue,
- change: revenueChange,
- changeType: revenueChange >= 0 ? 'positive' : 'negative',
- icon: 'lucideTrendingUp',
- color: 'success',
- format: 'currency',
- currency: 'XOF'
- },
- {
- title: 'Transactions Total',
- value: totalTransactions,
- change: transactionChange,
- changeType: transactionChange >= 0 ? 'positive' : 'negative',
- icon: 'lucideCreditCard',
- color: 'primary',
- format: 'number'
- },
- {
- title: 'Taux de Succès',
- value: successRate,
- change: successRateChange,
- changeType: successRateChange >= 0 ? 'positive' : 'negative',
- icon: 'lucideCheckCircle',
- color: 'info',
- format: 'percentage'
- },
- {
- title: 'Nouveaux Abonnements',
- value: 0, // À calculer via getDailySubscriptions
- change: 0,
- changeType: 'positive',
- icon: 'lucideUsers',
- color: 'warning',
- format: 'number'
- }
- ],
- paymentChart: this.preparePaymentChartData(monthlyTransactions),
- subscriptionStats: {
- total: 0, // À calculer via getMonthlySubscriptions
- active: 0,
- trial: 0,
- cancelled: 0,
- expired: 0,
- growthRate: 0
- },
- alerts: [],
- lastUpdated: new Date()
- };
- }
-
- /**
- * Calcule le revenue total des dernières transactions
- */
- private calculateTotalRevenue(transactions: TransactionReport[]): number {
- if (!transactions || transactions.length === 0) return 0;
-
- return transactions.reduce((sum, transaction) => sum + (transaction.revenue || 0), 0);
- }
-
- /**
- * Calcule le changement de revenue
- */
- private calculateRevenueChange(currentMonth: TransactionReport[], previousMonth: TransactionReport[]): number {
- const currentRevenue = this.calculateTotalRevenue(currentMonth);
- const previousRevenue = this.calculateTotalRevenue(previousMonth);
-
- if (previousRevenue === 0) return currentRevenue > 0 ? 100 : 0;
-
- return ((currentRevenue - previousRevenue) / previousRevenue) * 100;
- }
-
- /**
- * Calcule le total des transactions
- */
- private calculateTotalTransactions(transactions: TransactionReport[]): number {
- if (!transactions || transactions.length === 0) return 0;
-
- return transactions.reduce((sum, transaction) => sum + (transaction.total || 0), 0);
- }
-
- /**
- * Calcule le changement de transactions
- */
- private calculateTransactionChange(currentMonth: TransactionReport[], previousMonth: TransactionReport[]): number {
- const currentTransactions = this.calculateTotalTransactions(currentMonth);
- const previousTransactions = this.calculateTotalTransactions(previousMonth);
-
- if (previousTransactions === 0) return currentTransactions > 0 ? 100 : 0;
-
- return ((currentTransactions - previousTransactions) / previousTransactions) * 100;
- }
-
- /**
- * Calcule le taux de succès moyen
- */
- private calculateSuccessRate(transactions: TransactionReport[]): number {
- if (!transactions || transactions.length === 0) return 0;
-
- let totalSuccess = 0;
- let totalTransactions = 0;
-
- transactions.forEach(transaction => {
- totalSuccess += transaction.successful || 0;
- totalTransactions += transaction.total || 0;
- });
-
- if (totalTransactions === 0) return 0;
-
- return (totalSuccess / totalTransactions) * 100;
- }
-
- /**
- * Calcule le changement du taux de succès
- */
- private calculateSuccessRateChange(currentMonth: TransactionReport[], previousMonth: TransactionReport[]): number {
- const currentRate = this.calculateSuccessRate(currentMonth);
- const previousRate = this.calculateSuccessRate(previousMonth);
-
- if (previousRate === 0) return currentRate > 0 ? 100 : 0;
-
- return currentRate - previousRate;
- }
-
- /**
- * Prépare les données pour le graphique
- */
- private preparePaymentChartData(transactions: TransactionReport[]): PaymentChartData[] {
- if (!transactions || transactions.length === 0) return [];
-
- // Limiter aux 6 derniers mois pour le graphique
- const recentTransactions = transactions.slice(-6);
-
- return recentTransactions.map(transaction => ({
- period: this.formatMonthName(transaction.date),
- successful: transaction.successful || 0,
- failed: transaction.failed || 0,
- pending: transaction.pending || 0,
- revenue: transaction.revenue || 0
- }));
- }
-
- /**
- * Récupère les données du mois courant
- */
- private getCurrentMonthData(transactions: TransactionReport[]): TransactionReport[] {
- const currentMonth = new Date().getMonth();
- const currentYear = new Date().getFullYear();
-
- return transactions.filter(transaction => {
- const transactionDate = new Date(transaction.date);
- return transactionDate.getMonth() === currentMonth &&
- transactionDate.getFullYear() === currentYear;
- });
- }
-
- /**
- * Récupère les données du mois précédent
- */
- private getPreviousMonthData(transactions: TransactionReport[]): TransactionReport[] {
- const now = new Date();
- const previousMonth = now.getMonth() === 0 ? 11 : now.getMonth() - 1;
- const year = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();
-
- return transactions.filter(transaction => {
- const transactionDate = new Date(transaction.date);
- return transactionDate.getMonth() === previousMonth &&
- transactionDate.getFullYear() === year;
- });
- }
-
- /**
- * Format une date au format YYYY-MM-DD
- */
- private formatDate(date: Date): string {
- return date.toISOString().split('T')[0];
- }
-
- /**
- * Format le nom du mois à partir d'une date
- */
- private formatMonthName(dateString: string): string {
- const date = new Date(dateString);
- const months = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'];
- return months[date.getMonth()];
- }
-
- /**
- * Vide le cache
- */
- clearCache(): void {
- this.cache.clear();
- }
-
- /**
- * Rafraîchit les données
- */
- refreshData(): Observable {
- this.clearCache();
- return this.getDcbDashboardData();
- }
-}
\ No newline at end of file
diff --git a/src/app/modules/dcb-dashboard/services/dcb-reporting.service.ts b/src/app/modules/dcb-dashboard/services/dcb-reporting.service.ts
index 306c697..40ddc20 100644
--- a/src/app/modules/dcb-dashboard/services/dcb-reporting.service.ts
+++ b/src/app/modules/dcb-dashboard/services/dcb-reporting.service.ts
@@ -1,10 +1,23 @@
-import { Injectable, inject } from '@angular/core';
+import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
-import { Observable, of } from 'rxjs';
-import { map, catchError } from 'rxjs/operators';
+import { Observable } from 'rxjs';
+import { map, timeout } from 'rxjs/operators';
import { environment } from '@environments/environment';
-// Interfaces pour les réponses de l'API
+// ============ INTERFACES EXPORTÉES ============
+export interface TransactionReport {
+ type: 'transaction';
+ period: 'daily' | 'weekly' | 'monthly';
+ startDate: string;
+ endDate: string;
+ merchantPartnerId?: number;
+ totalAmount: number;
+ totalCount: number;
+ items: TransactionItem[];
+ summary: TransactionSummary;
+ generatedAt: string;
+}
+
export interface TransactionItem {
period: string;
totalAmount: number;
@@ -16,20 +29,22 @@ export interface TransactionItem {
merchantPartnerId?: number;
}
-export interface TransactionResponse {
- type: string;
- period: string;
+export interface TransactionSummary {
+ avgAmount: number;
+ minAmount: number;
+ maxAmount: number;
+}
+
+export interface SubscriptionReport {
+ type: 'subscription';
+ period: 'daily' | 'weekly' | 'monthly';
startDate: string;
endDate: string;
merchantPartnerId?: number;
totalAmount: number;
totalCount: number;
- items: TransactionItem[];
- summary: {
- avgAmount: number;
- minAmount: number;
- maxAmount: number;
- };
+ items: SubscriptionItem[];
+ summary: SubscriptionSummary;
generatedAt: string;
}
@@ -39,218 +54,115 @@ export interface SubscriptionItem {
count: number;
activeCount: number;
cancelledCount: number;
-}
-
-export interface SubscriptionResponse {
- type: string;
- period: string;
- startDate: string;
- endDate: string;
merchantPartnerId?: number;
- totalAmount: number;
- totalCount: number;
- items: SubscriptionItem[];
- summary: {
- avgAmount: number;
- minAmount: number;
- maxAmount: number;
- };
- generatedAt: string;
}
-@Injectable({ providedIn: 'root' })
+export interface SubscriptionSummary {
+ avgAmount: number;
+ minAmount: number;
+ maxAmount: number;
+}
+
+export interface SyncResponse {
+ message: string;
+ timestamp: string;
+}
+
+export interface ReportParams {
+ startDate?: string;
+ endDate?: string;
+ merchantPartnerId?: number;
+}
+
+@Injectable({
+ providedIn: 'root'
+})
export class DcbReportingService {
- private http = inject(HttpClient);
- private reportingApiUrl = `${environment.reportingApiUrl}/reporting`;
+ private baseUrl = environment.reportingApiUrl + '/reporting';
- /**
- * Récupère les transactions journalières
- */
- getDailyTransactions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/transactions/daily`;
- console.log('Fetching daily transactions from:', url, 'with params:', params.toString());
-
- return this.http.get(url, { params }).pipe(
- map(response => {
- console.log('Daily transactions raw response:', response);
- return response?.items || [];
- }),
- catchError((error) => {
- console.error('Error fetching daily transactions:', error);
- return of([]);
- })
+ constructor(private http: HttpClient) {}
+
+ healthCheck(endpoint: string): Observable {
+ return this.http.get(`${this.baseUrl}/${endpoint}`, {
+ observe: 'response',
+ responseType: 'text'
+ }).pipe(
+ timeout(5000),
+ map(response => ({
+ status: 'success',
+ statusCode: response.status
+ }))
);
}
- /**
- * Récupère les transactions hebdomadaires
- */
- getWeeklyTransactions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/transactions/weekly`;
- console.log('Fetching weekly transactions from:', url);
-
- return this.http.get(url, { params }).pipe(
- map(response => {
- console.log('Weekly transactions raw response:', response);
- return response?.items || [];
- }),
- catchError((error) => {
- console.error('Error fetching weekly transactions:', error);
- return of([]);
- })
+ syncHealthCheck(): Observable {
+ return this.http.post(`${this.baseUrl}/sync/full`, {}, {
+ observe: 'response',
+ responseType: 'text'
+ }).pipe(
+ timeout(5000),
+ map(response => ({
+ status: 'success',
+ statusCode: response.status,
+ service: 'Synchronisation'
+ }))
);
}
- /**
- * Récupère les transactions mensuelles
- */
- getMonthlyTransactions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
+ // === TRANSACTIONS ===
+ getDailyTransactions(params?: ReportParams): Observable {
+ let httpParams = new HttpParams();
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
+ if (params?.startDate) {
+ httpParams = httpParams.set('startDate', params.startDate);
+ }
+ if (params?.endDate) {
+ httpParams = httpParams.set('endDate', params.endDate);
+ }
+ if (params?.merchantPartnerId) {
+ httpParams = httpParams.set('merchantPartnerId', params.merchantPartnerId.toString());
+ }
- const url = `${this.reportingApiUrl}/transactions/monthly`;
- console.log('Fetching monthly transactions from:', url);
-
- return this.http.get(url, { params }).pipe(
- map(response => {
- console.log('Monthly transactions raw response:', response);
- return response?.items || [];
- }),
- catchError((error) => {
- console.error('Error fetching monthly transactions:', error);
- return of([]);
- })
- );
+ return this.http.get(`${this.baseUrl}/transactions/daily`, { params: httpParams });
}
- /**
- * Récupère les abonnements journaliers
- */
- getDailySubscriptions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/subscriptions/daily`;
- console.log('Fetching daily subscriptions from:', url);
-
- return this.http.get(url, { params }).pipe(
- map(response => {
- console.log('Daily subscriptions raw response:', response);
- return response?.items || [];
- }),
- catchError((error) => {
- console.error('Error fetching daily subscriptions:', error);
- return of([]);
- })
- );
+ getWeeklyTransactions(): Observable {
+ return this.http.get(`${this.baseUrl}/transactions/weekly`);
}
- /**
- * Récupère les abonnements hebdomadaires
- */
- getWeeklySubscriptions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
-
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/subscriptions/weekly`;
- console.log('Fetching weekly subscriptions from:', url);
-
- return this.http.get(url, { params }).pipe(
- map(response => {
- console.log('Weekly subscriptions raw response:', response);
- return response?.items || [];
- }),
- catchError((error) => {
- console.error('Error fetching weekly subscriptions:', error);
- return of([]);
- })
- );
+ getMonthlyTransactions(): Observable {
+ return this.http.get(`${this.baseUrl}/transactions/monthly`);
}
- /**
- * Récupère les abonnements mensuels
- */
- getMonthlySubscriptions(
- startDate?: string,
- endDate?: string,
- merchantPartnerId?: number
- ): Observable {
- let params = new HttpParams();
+ getTransactionsWithDates(startDate: string, endDate: string): Observable {
+ const params = new HttpParams()
+ .set('startDate', startDate)
+ .set('endDate', endDate);
- if (startDate) params = params.set('startDate', startDate);
- if (endDate) params = params.set('endDate', endDate);
- if (merchantPartnerId) params = params.set('merchantPartnerId', merchantPartnerId.toString());
-
- const url = `${this.reportingApiUrl}/subscriptions/monthly`;
- console.log('Fetching monthly subscriptions from:', url);
-
- return this.http.get(url, { params }).pipe(
- map(response => {
- console.log('Monthly subscriptions raw response:', response);
- return response?.items || [];
- }),
- catchError((error) => {
- console.error('Error fetching monthly subscriptions:', error);
- return of([]);
- })
- );
+ return this.http.get(`${this.baseUrl}/transactions/daily`, { params });
}
- /**
- * Format une date au format YYYY-MM-DD
- */
+ // === SUBSCRIPTIONS ===
+ getDailySubscriptions(): Observable {
+ return this.http.get(`${this.baseUrl}/subscriptions/daily`);
+ }
+
+ getMonthlySubscriptions(merchantPartnerId?: number): Observable {
+ let params = new HttpParams();
+ if (merchantPartnerId) {
+ params = params.set('merchantPartnerId', merchantPartnerId.toString());
+ }
+
+ return this.http.get(`${this.baseUrl}/subscriptions/monthly`, { params });
+ }
+
+ // === SYNC ===
+ triggerManualSync(): Observable {
+ return this.http.post(`${this.baseUrl}/sync/full`, {});
+ }
+
+ // === UTILS ===
formatDate(date: Date): string {
- const year = date.getFullYear();
- const month = (date.getMonth() + 1).toString().padStart(2, '0');
- const day = date.getDate().toString().padStart(2, '0');
- return `${year}-${month}-${day}`;
- }
-
- /**
- * Vide le cache
- */
- clearCache(): void {
- // Pas de cache dans cette version simplifiée
+ return date.toISOString().split('T')[0];
}
}
\ No newline at end of file
diff --git a/test_endpoints.bat b/test_endpoints.bat
new file mode 100644
index 0000000..5db8052
--- /dev/null
+++ b/test_endpoints.bat
@@ -0,0 +1,81 @@
+@echo off
+set OUTPUT=api_results.txt
+echo =========================================================================== > %OUTPUT%
+echo RESULTATS DES TESTS API >> %OUTPUT%
+echo =========================================================================== >> %OUTPUT%
+echo Date: %date% >> %OUTPUT%
+echo Heure: %time% >> %OUTPUT%
+echo =========================================================================== >> %OUTPUT%
+echo. >> %OUTPUT%
+
+set i=1
+
+:test1
+echo [!i!] Test transactions/daily...
+echo *** ENDPOINT !i! : Transactions journalieres (global) *** >> %OUTPUT%
+curl -s https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/transactions/daily >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+set /a i+=1
+
+:test2
+echo [!i!] Test transactions/daily?merchantPartnerId=4...
+echo *** ENDPOINT !i! : Transactions journalieres (merchant 4) *** >> %OUTPUT%
+curl -s "https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/transactions/daily?merchantPartnerId=4" >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+set /a i+=1
+
+:test3
+echo [!i!] Test transactions/weekly...
+echo *** ENDPOINT !i! : Transactions hebdomadaires *** >> %OUTPUT%
+curl -s https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/transactions/weekly >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+set /a i+=1
+
+:test4
+echo [!i!] Test transactions/monthly...
+echo *** ENDPOINT !i! : Transactions mensuelles *** >> %OUTPUT%
+curl -s https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/transactions/monthly >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+set /a i+=1
+
+:test5
+echo [!i!] Test transactions avec dates...
+echo *** ENDPOINT !i! : Transactions avec dates (01-30 nov 2024) *** >> %OUTPUT%
+curl -s "https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/transactions/daily?startDate=2024-11-01&endDate=2024-11-30" >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+set /a i+=1
+
+:test6
+echo [!i!] Test subscriptions/daily...
+echo *** ENDPOINT !i! : Subscriptions journalieres *** >> %OUTPUT%
+curl -s https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/subscriptions/daily >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+set /a i+=1
+
+:test7
+echo [!i!] Test subscriptions/monthly?merchantPartnerId=4...
+echo *** ENDPOINT !i! : Subscriptions mensuelles (merchant 4) *** >> %OUTPUT%
+curl -s "https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/subscriptions/monthly?merchantPartnerId=4" >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+set /a i+=1
+
+:test8
+echo [!i!] Test sync/full (POST)...
+echo *** ENDPOINT !i! : Synchronisation manuelle (POST) *** >> %OUTPUT%
+curl -s -X POST https://api-reporting-service.dcb.pixpay.sn/api/v1/reporting/sync/full >> %OUTPUT%
+echo. >> %OUTPUT% & echo. >> %OUTPUT%
+
+echo =========================================================================== >> %OUTPUT%
+echo TESTS TERMINES - !i! endpoints >> %OUTPUT%
+echo =========================================================================== >> %OUTPUT%
+
+echo.
+echo ========================================
+echo TOUS LES TESTS SONT TERMINES!
+echo Resultats dans: %OUTPUT%
+echo ========================================
+echo.
+echo Pour voir les resultats:
+echo type %OUTPUT%
+echo.
+pause
\ No newline at end of file