From ef0ba8237dd39cdb9c67fd748bfa7d6e3764756b Mon Sep 17 00:00:00 2001 From: diallolatoile Date: Tue, 2 Dec 2025 00:33:12 +0000 Subject: [PATCH] feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature --- api_results.txt | 34 + package-lock.json | 8 +- package.json | 2 +- .../components/active-subscriptions.ts | 192 --- .../components/dcb-dashboard-report.ts | 617 ---------- .../components/dcb-reporting-dashboard.css | 212 ++++ .../components/dcb-reporting-dashboard.html | 601 +++++++++ .../components/dcb-reporting-dashboard.ts | 1069 +++++++++++++++++ .../dcb-dashboard/components/payment-stats.ts | 412 ------- .../components/recent-transactions.ts | 247 ---- .../components/subscription-overview.ts | 307 ----- .../components/success-rate-chart.ts | 281 ----- .../modules/dcb-dashboard/dcb-dashboard.html | 2 +- .../dcb-dashboard/dcb-dashboard.spec.ts | 10 +- .../modules/dcb-dashboard/dcb-dashboard.ts | 4 +- .../models/dcb-dashboard.models.ts | 105 -- .../models/dcb-reporting.models.ts | 68 ++ .../services/dcb-dashboard.service.ts | 459 ------- .../services/dcb-reporting.service.ts | 318 ++--- test_endpoints.bat | 81 ++ 20 files changed, 2193 insertions(+), 2836 deletions(-) create mode 100644 api_results.txt delete mode 100644 src/app/modules/dcb-dashboard/components/active-subscriptions.ts delete mode 100644 src/app/modules/dcb-dashboard/components/dcb-dashboard-report.ts create mode 100644 src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.css create mode 100644 src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.html create mode 100644 src/app/modules/dcb-dashboard/components/dcb-reporting-dashboard.ts delete mode 100644 src/app/modules/dcb-dashboard/components/payment-stats.ts delete mode 100644 src/app/modules/dcb-dashboard/components/recent-transactions.ts delete mode 100644 src/app/modules/dcb-dashboard/components/subscription-overview.ts delete mode 100644 src/app/modules/dcb-dashboard/components/success-rate-chart.ts delete mode 100644 src/app/modules/dcb-dashboard/models/dcb-dashboard.models.ts create mode 100644 src/app/modules/dcb-dashboard/models/dcb-reporting.models.ts delete mode 100644 src/app/modules/dcb-dashboard/services/dcb-dashboard.service.ts create mode 100644 test_endpoints.bat 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: ` -
-
-
-
Abonnements Actifs
- -
-
-
- -
-
- Chargement... -
-
- - -
-
-
-
Total actifs
-

- - {{ formatNumber(totalSubscriptions) }} - -

-
-
- -
-
- - -
-
- Taux d'activité - {{ activePercentage | number:'1.1-1' }}% -
- -
- -
-
- Nouveaux -
{{ newSubscriptionsToday }}
-
-
- Annulés -
{{ cancelledSubscriptions }}
-
-
-
- - -
-
- - {{ error }} -
-
-
- -
- `, - 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) }} - - - {{ dailyTransactions.successRate | number:'1.1-1' }}% - -
-
-
-
- - -
-
-
- -
Transactions Hebdomadaires
-

- - {{ weeklyTransactions.total }} - -

-

Transactions

-
- {{ formatCurrency(weeklyTransactions.revenue) }} - - - {{ weeklyTransactions.successRate | number:'1.1-1' }}% - -
-
-
-
- - -
-
-
- -
Transactions Mensuelles
-

- - {{ monthlyTransactions.total }} - -

-

Transactions

-
- {{ formatCurrency(monthlyTransactions.revenue) }} - - - {{ monthlyTransactions.successRate | number:'1.1-1' }}% - -
-
-
-
- - -
-
-
- -
Taux global
-

- - {{ overallSuccessRate | number:'1.1-1' }} - % -

-

Succès (30 jours)

-
- Période: 30j - - {{ getPerformanceLabel(overallSuccessRate) }} - -
-
-
-
-
- - -
- -
-
-
-
Revenue Mensuel
-
-
- -
-
-
- - -
-
-
-
-
Abonnements
-
- -
- - - -
-
-
-
-
- -
-
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 - -
-
-
-
-
- - -
-
-
-
-
-
Transactions Récentes
-
- -
- - - -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
PériodeTransactionsMontant TotalRéussiesÉchouéesEn attenteTaux de succès
{{ item.period }}{{ item.count }}{{ formatCurrency(item.totalAmount) }}{{ item.successCount }}{{ item.failedCount }}{{ item.pendingCount }} - - {{ 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 @@ +
+ +
+
+
+

+ + Dashboard Reporting DCB +

+

Surveillance en temps réel des transactions et abonnements

+
+
+ +
+ + +
+ + +
+
+
+ + + + +
+ +
+ + + + + + +
+ + +
+
+
+
+
+ + +
+
+
+
+ + 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 + +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+ + Évolution du Revenue +
+

Courbe des revenus par période

+
+
+ + +
+
+
+
+
+
+

Chargement des données...

+
+
+ +

Aucune donnée disponible

+
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + Performance +
+
+
+
+
+ +
+

+ {{ 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 +
+
+
+
+
+ + +
+
+
+
+
+ + Santé du système +
+ +
+
+
+
+
+
+
+
+ {{ service.service }} +
+
+ + {{ service.responseTime }}ms + + + {{ service.status }} + +
+
+
+
+
+ + + Dernière vérification: {{ formatTimeAgo(systemHealth[0]?.lastChecked) }} + +
+
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+ + Transactions récentes +
+

Dernières 24 heures

+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
Date/HeureMontantStatutTaux
+
{{ item.period }}
+ {{ formatNumber(item.count) }} transactions +
+
{{ formatCurrency(item.totalAmount) }}
+
+ + {{ item.successCount || 0 }} ✓ + + + {{ item.failedCount || 0 }} ✗ + + + + {{ (item.count > 0 ? (item.successCount / item.count) * 100 : 0) | number:'1.1-1' }}% + +
+ +

Aucune transaction disponible

+
+
+
+ +
+
+ + +
+
+
+
+
+
+ + Alertes système +
+

Notifications en temps réel

+
+
+ + {{ alerts.length }} + +
+
+
+
+
+
+
+
+ + + + +
+
+
+
{{ 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: ` -
-
-
-
Statistiques des Paiements
- -
-
- -
- -
-
- Chargement... -
-

Chargement des statistiques...

-
- - -
- -
-
-
- -
Journalier
-

- - {{ dailyStats.transactions }} - -

-

Transactions

-
- {{ formatCurrency(dailyStats.revenue) }} - - - {{ dailyStats.successRate | number:'1.1-1' }}% - -
-
-
-
- - -
-
-
- -
Hebdomadaire
-

- - {{ weeklyStats.transactions }} - -

-

Transactions

-
- {{ formatCurrency(weeklyStats.revenue) }} - - - {{ weeklyStats.successRate | number:'1.1-1' }}% - -
-
-
-
- - -
-
-
- -
Mensuel
-

- - {{ monthlyStats.transactions }} - -

-

Transactions

-
- {{ formatCurrency(monthlyStats.revenue) }} - - - {{ monthlyStats.successRate | number:'1.1-1' }}% - -
-
-
-
- - -
-
-
- -
Taux global
-

- - {{ overallSuccessRate | number:'1.1-1' }} - % -

-

Succès (30 jours)

-
- Période: 30j - - {{ getPerformanceLabel(overallSuccessRate) }} - -
-
-
-
-
- - -
-
-
-
-
- Performance globale: - - {{ 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: ` -
-
-
-

Abonnements Récents

-
- - Voir tout -
-
-
- -
- -
-
- Chargement... -
-

Chargement des abonnements...

-
- - -
- -

{{ error }}

- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
IDDate de créationStatutMontantPé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é

-
-
-
- - - -
-
- `, - 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: ` -
-
-
-

Aperçu des Abonnements

-
- - - {{ formatNumber(totalSubscriptions) }} total - - -
-
-
- -
- -
-
- Chargement... -
-

Chargement des données...

-
- - -
- -
- -
- - -
-
-
-
-
-
- -
-
-
-
-
Actifs
-
- {{ formatNumber(activeSubscriptions) }} - - - +{{ 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: ` -
-
-

Taux de Succès des Transactions

-
-
- -
-
- Chargement... -
-
- - -
-
-
-

{{ 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