-
Nouveaux
-
156
+
+
+
+
+
Total actifs
+
+
+ {{ formatNumber(totalSubscriptions) }}
+
+
+
+
+
+
-
-
Renouvellements
-
89%
+
+
+
+
+ Taux d'activité
+ {{ activePercentage | number:'1.1-1' }}%
+
+
+
+
+
+
+ Nouveaux
+
{{ newSubscriptionsToday }}
+
+
+ Annulés
+
{{ cancelledSubscriptions }}
+
+
+
+
+
+
-
`,
+ styles: [`
+ .spin {
+ animation: spin 1s linear infinite;
+ }
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+ .alert-sm {
+ padding: 0.375rem 0.75rem;
+ font-size: 0.875rem;
+ }
+ `]
})
-export class ActiveSubscriptions {}
\ No newline at end of file
+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/alert-widget.ts b/src/app/modules/dcb-dashboard/components/alert-widget.ts
deleted file mode 100644
index 01fb6cd..0000000
--- a/src/app/modules/dcb-dashboard/components/alert-widget.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Component } from '@angular/core';
-import { NgIconComponent } from '@ng-icons/core';
-
-@Component({
- selector: 'app-alert-widget',
- imports: [NgIconComponent],
- template: `
-
-
-
-
-
-
-
-
-
-
Taux d'échec élevé Airtel
-
Le taux d'échec a augmenté de 15%
-
Il y a 30 min
-
-
-
-
-
-
-
-
-
Maintenance planifiée
-
Maintenance ce soir de 22h à 00h
-
Il y a 2h
-
-
-
-
-
-
-
-
-
Nouveau partenaire
-
MTN Sénégal configuré avec succès
-
Il y a 4h
-
-
-
-
-
-
-
-
- `,
-})
-export class AlertWidget {}
\ 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
new file mode 100644
index 0000000..d970f96
--- /dev/null
+++ b/src/app/modules/dcb-dashboard/components/dcb-dashboard-report.ts
@@ -0,0 +1,612 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Chartjs } from '@app/components/chartjs';
+import { ChartConfiguration } from 'chart.js';
+import { getColor } from '@/app/utils/color-utils';
+import { NgbProgressbarModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgIconComponent } from '@ng-icons/core';
+import { CountUpModule } from 'ngx-countup';
+import { DcbReportingService, TransactionItem, SubscriptionItem } from '../services/dcb-reporting.service';
+import { catchError } from 'rxjs/operators';
+import { of, Subscription } from 'rxjs';
+
+@Component({
+ selector: 'app-dashboard-report',
+ imports: [
+ CommonModule,
+ Chartjs,
+ NgbProgressbarModule,
+ NgbDropdownModule, // Ajoutez ce module
+ NgIconComponent,
+ CountUpModule
+ ],
+ template: `
+
+
+
+
+
+
Dashboard Reporting
+
+
+
+
+
+
+
+
+
+
+
+
+
Transactions Journalières
+
+
+ {{ dailyTransactions.total }}
+
+
+
Transactions
+
+ {{ formatCurrency(dailyTransactions.revenue) }}
+ = 95 ? 'text-success' : 'text-warning'">
+
+ {{ dailyTransactions.successRate | number:'1.1-1' }}%
+
+
+
+
+
+
+
+
+
+
+
+
Transactions Hebdomadaires
+
+
+ {{ weeklyTransactions.total }}
+
+
+
Transactions
+
+ {{ formatCurrency(weeklyTransactions.revenue) }}
+ = 95 ? 'text-success' : 'text-warning'">
+
+ {{ weeklyTransactions.successRate | number:'1.1-1' }}%
+
+
+
+
+
+
+
+
+
+
+
+
Transactions Mensuelles
+
+
+ {{ monthlyTransactions.total }}
+
+
+
Transactions
+
+ {{ formatCurrency(monthlyTransactions.revenue) }}
+ = 95 ? 'text-success' : 'text-warning'">
+
+ {{ monthlyTransactions.successRate | number:'1.1-1' }}%
+
+
+
+
+
+
+
+
+
+
+
+
Taux global
+
+
+ {{ overallSuccessRate | number:'1.1-1' }}
+ %
+
+
Succès (30 jours)
+
+ Période: 30j
+ = 95 ? 'text-success' : overallSuccessRate >= 90 ? 'text-warning' : 'text-danger'">
+ {{ getPerformanceLabel(overallSuccessRate) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Journaliers
+
+
+
+
{{ dailySubscriptions.active }}
+ Actifs
+
+
+ Total: {{ dailySubscriptions.total }}
+ Annulés: {{ dailySubscriptions.cancelled }}
+
+
+
+
+
+
Hebdomadaires
+
+
+
+
{{ weeklySubscriptions.active }}
+ Actifs
+
+
+ Total: {{ weeklySubscriptions.total }}
+ Annulés: {{ weeklySubscriptions.cancelled }}
+
+
+
+
+
+
Mensuels
+
+
+
+
{{ monthlySubscriptions.active }}
+ Actifs
+
+
+ Total: {{ monthlySubscriptions.total }}
+ Annulés: {{ monthlySubscriptions.cancelled }}
+
+
+
+
+
+
+
+ Taux d'activité
+ {{ getCurrentActivityRate() | number:'1.1-1' }}%
+
+
+
+ {{ getCurrentActiveSubscriptions() }} actifs sur {{ getCurrentTotalSubscriptions() }} total
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Période |
+ Transactions |
+ Montant Total |
+ Réussies |
+ Échouées |
+ En attente |
+ Taux de succès |
+
+
+
+
+ | {{ item.period }} |
+ {{ item.count }} |
+ {{ formatCurrency(item.totalAmount) }} |
+ {{ item.successCount }} |
+ {{ item.failedCount }} |
+ {{ item.pendingCount }} |
+
+ = 95 ? 'text-success' : getSuccessRate(item) >= 90 ? 'text-warning' : 'text-danger'">
+ {{ getSuccessRate(item) | number:'1.1-1' }}%
+
+ |
+
+
+ |
+ Aucune donnée de transaction disponible
+ |
+
+
+
+
+
+
+
+
+
+ `,
+ styles: [`
+ .spin {
+ animation: spin 1s linear infinite;
+ }
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+ `]
+})
+export class DashboardReport implements OnInit, OnDestroy {
+
+ // Périodes d'affichage
+ subscriptionPeriod: 'daily' | 'weekly' | 'monthly' = 'daily';
+ transactionPeriod: 'daily' | 'weekly' | 'monthly' = 'daily';
+
+ // Données des transactions
+ dailyTransactions = { total: 0, revenue: 0, successRate: 0 };
+ weeklyTransactions = { total: 0, revenue: 0, successRate: 0 };
+ monthlyTransactions = { total: 0, revenue: 0, successRate: 0 };
+ overallSuccessRate = 0;
+
+ // Données brutes pour les tableaux
+ dailyTransactionItems: TransactionItem[] = [];
+ weeklyTransactionItems: TransactionItem[] = [];
+ monthlyTransactionItems: TransactionItem[] = [];
+
+ // Données des abonnements
+ dailySubscriptions = { total: 0, active: 0, cancelled: 0 };
+ weeklySubscriptions = { total: 0, active: 0, cancelled: 0 };
+ monthlySubscriptions = { total: 0, active: 0, cancelled: 0 };
+
+ // Données brutes pour les abonnements
+ dailySubscriptionItems: SubscriptionItem[] = [];
+ weeklySubscriptionItems: SubscriptionItem[] = [];
+ monthlySubscriptionItems: SubscriptionItem[] = [];
+
+ // Données pour le graphique
+ chartData: { date: string; revenue: number }[] = [];
+
+ // Abonnements aux API
+ private subscriptions: Subscription[] = [];
+
+ constructor(private reportingService: DcbReportingService) {}
+
+ ngOnInit() {
+ 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/operator-performance.ts b/src/app/modules/dcb-dashboard/components/operator-performance.ts
deleted file mode 100644
index c6cc170..0000000
--- a/src/app/modules/dcb-dashboard/components/operator-performance.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Component } from '@angular/core';
-import { NgIconComponent } from '@ng-icons/core';
-
-@Component({
- selector: 'app-operator-performance',
- imports: [NgIconComponent],
- template: `
-
-
-
-
-
Performance Opérateurs
-
-
-
-
-
-
-
-
-
-
- `,
-})
-export class OperatorPerformance {}
\ 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
index 604ba39..950415f 100644
--- a/src/app/modules/dcb-dashboard/components/payment-stats.ts
+++ b/src/app/modules/dcb-dashboard/components/payment-stats.ts
@@ -1,17 +1,37 @@
-import { Component } from '@angular/core';
+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: [NgIconComponent, CountUpModule],
+ imports: [CommonModule, NgIconComponent, CountUpModule],
template: `
+
-
+
+
+
+ Chargement...
+
+
Chargement des statistiques...
+
+
+
+
@@ -19,14 +39,16 @@ import { CountUpModule } from 'ngx-countup';
Journalier
- 342
+
+ {{ dailyStats.transactions }}
+
Transactions
- 245K XOF
-
-
- 98.2%
+ {{ formatCurrency(dailyStats.revenue) }}
+ = 95 ? 'text-success' : 'text-warning'">
+
+ {{ dailyStats.successRate | number:'1.1-1' }}%
@@ -40,14 +62,16 @@ import { CountUpModule } from 'ngx-countup';
Hebdomadaire
- 2,150
+
+ {{ weeklyStats.transactions }}
+
Transactions
- 1.58M XOF
-
-
- 97.8%
+ {{ formatCurrency(weeklyStats.revenue) }}
+ = 95 ? 'text-success' : 'text-warning'">
+
+ {{ weeklyStats.successRate | number:'1.1-1' }}%
@@ -61,35 +85,38 @@ import { CountUpModule } from 'ngx-countup';
Mensuel
- 8,450
+
+ {{ monthlyStats.transactions }}
+
Transactions
- 6.25M XOF
-
-
- 96.5%
+ {{ formatCurrency(monthlyStats.revenue) }}
+ = 95 ? 'text-success' : 'text-warning'">
+
+ {{ monthlyStats.successRate | number:'1.1-1' }}%
-
+
-
-
Annuel
+
+
Taux global
- 12,500
+
+ {{ overallSuccessRate | number:'1.1-1' }}
+ %
-
Transactions
+
Succès (30 jours)
- 9.85M XOF
-
-
- 95.2%
+ Période: 30j
+ = 95 ? 'text-success' : overallSuccessRate >= 90 ? 'text-warning' : 'text-danger'">
+ {{ getPerformanceLabel(overallSuccessRate) }}
@@ -98,16 +125,22 @@ import { CountUpModule } from 'ngx-countup';
-
+
Performance globale:
- 97.4% de taux de succès
+ = 95 ? 'text-success' : overallSuccessRate >= 90 ? 'text-warning' : 'text-danger'">
+ {{ overallSuccessRate | number:'1.1-1' }}% de taux de succès
+
+
+ ({{ dailyStats.transactions }} transactions aujourd'hui)
+
- Dernière mise à jour: {{ getCurrentTime() }}
+
+ Mise à jour: {{ lastUpdated | date:'HH:mm' }}
@@ -116,12 +149,264 @@ import { CountUpModule } from 'ngx-countup';
`,
+ styles: [`
+ .spin {
+ animation: spin 1s linear infinite;
+ }
+ @keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+ }
+ `]
})
-export class PaymentStats {
- getCurrentTime(): string {
- return new Date().toLocaleTimeString('fr-FR', {
- hour: '2-digit',
- minute: '2-digit'
+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
index 89874bd..2ed4746 100644
--- a/src/app/modules/dcb-dashboard/components/recent-transactions.ts
+++ b/src/app/modules/dcb-dashboard/components/recent-transactions.ts
@@ -1,190 +1,247 @@
-import { Component } from '@angular/core';
-import { NgIconComponent } from '@ng-icons/core';
-import { DecimalPipe } from '@angular/common';
-import { NgbDropdownModule, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
-
-interface Transaction {
- id: string;
- user: string;
- operator: string;
- amount: number;
- status: 'success' | 'pending' | 'failed';
- date: string;
-}
+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: [
- NgIconComponent,
- DecimalPipe,
- NgbPaginationModule,
- NgbDropdownModule,
- ],
+ imports: [CommonModule],
template: `
-