feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature

This commit is contained in:
diallolatoile 2025-12-15 04:43:58 +00:00
parent 02d58ba4fa
commit 296fe413a9
11 changed files with 877 additions and 871 deletions

View File

@ -62,13 +62,13 @@ export class AuthService {
private readonly tokenKey = 'access_token'; private readonly tokenKey = 'access_token';
private readonly refreshTokenKey = 'refresh_token'; private readonly refreshTokenKey = 'refresh_token';
private readonly dashboardAccessService = inject(DashboardAccessService);
private readonly transactionAccessService = inject(TransactionAccessService);
private authState$ = new BehaviorSubject<boolean>(this.isAuthenticated()); private authState$ = new BehaviorSubject<boolean>(this.isAuthenticated());
private userProfile$ = new BehaviorSubject<User | null>(null); private userProfile$ = new BehaviorSubject<User | null>(null);
private initialized$ = new BehaviorSubject<boolean>(false); private initialized$ = new BehaviorSubject<boolean>(false);
private readonly dashboardAccessService = inject(DashboardAccessService);
private readonly transactionAccessService = inject(TransactionAccessService);
// === INITIALISATION DE L'APPLICATION === // === INITIALISATION DE L'APPLICATION ===
/** /**

View File

@ -128,7 +128,6 @@ export class UserProfileComponent implements OnInit, OnDestroy {
* Charge le profil utilisateur explicitement * Charge le profil utilisateur explicitement
*/ */
loadUserProfile(): void { loadUserProfile(): void {
console.log('🚀 Loading user profile...');
this.isLoading = true; this.isLoading = true;
this.hasError = false; this.hasError = false;
this.cdr.detectChanges(); this.cdr.detectChanges();
@ -137,7 +136,6 @@ export class UserProfileComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (profile) => { next: (profile) => {
console.log('✅ Profile loaded successfully:', profile.username);
// Note: le profil sera automatiquement mis à jour via la subscription getUserProfile() // Note: le profil sera automatiquement mis à jour via la subscription getUserProfile()
this.isLoading = false; this.isLoading = false;
this.cdr.detectChanges(); this.cdr.detectChanges();
@ -240,13 +238,7 @@ export class UserProfileComponent implements OnInit, OnDestroy {
* Obtient l'URL de l'avatar de l'utilisateur * Obtient l'URL de l'avatar de l'utilisateur
*/ */
getUserAvatar(): string { getUserAvatar(): string {
if (!this.user) { return `assets/images/users/user-2.jpg`;
return 'assets/images/users/user-default.jpg';
}
// Vous pouvez implémenter une logique pour générer un avatar personnalisé
// ou utiliser une image par défaut basée sur l'email/nom
return `assets/images/users/user-${(this.user.id?.charCodeAt(0) % 5) + 1}.jpg`;
} }
/** /**
@ -254,7 +246,7 @@ export class UserProfileComponent implements OnInit, OnDestroy {
*/ */
onAvatarError(event: Event): void { onAvatarError(event: Event): void {
const img = event.target as HTMLImageElement; const img = event.target as HTMLImageElement;
img.src = 'assets/images/users/user-default.jpg'; img.src = 'assets/images/users/user-2.jpg';
img.onerror = null; // Éviter les boucles infinies img.onerror = null;
} }
} }

View File

@ -1,768 +0,0 @@
<div class="container-fluid dashboard-container">
<!-- Header avec navigation -->
<div class="dashboard-header">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0 text-primary">
<ng-icon name="lucideLayoutDashboard" class="me-2"></ng-icon>
Dashboard FinTech Reporting
<span *ngIf="access.isMerchantUser" class="badge bg-success ms-2">
<ng-icon name="lucideStore" class="me-1"></ng-icon>
Merchant {{ merchantId }}
</span>
<span *ngIf="access.isHubUser" class="badge bg-primary ms-2">
<ng-icon name="lucideShield" class="me-1"></ng-icon>
Hub Admin
</span>
</h1>
<p class="text-muted mb-0">
<ng-icon [name]="currentRoleIcon" class="me-1"></ng-icon>
{{ currentRoleLabel }} - {{ getCurrentMerchantName() }}
</p>
</div>
<div class="d-flex gap-2 align-items-center">
<!-- Contrôles rapides -->
<div class="btn-group btn-group-sm" role="group">
<!-- Bouton Actualiser selon le type -->
<button class="btn btn-outline-primary btn-sm"
(click)="refreshData()"
[disabled]="loading.globalData || loading.merchantData">
<ng-icon name="lucideRefreshCw"
[class.spin]="loading.globalData || loading.merchantData"
class="me-1"></ng-icon>
{{ (loading.globalData || loading.merchantData) ? 'Chargement...' : 'Actualiser' }}
</button>
<!-- Bouton Sync seulement si autorisé -->
<button *ngIf="canTriggerSync()"
class="btn btn-outline-danger btn-sm"
(click)="triggerSync()"
[disabled]="loading.sync">
<ng-icon name="lucideRefreshCcw" [class.spin]="loading.sync" class="me-1"></ng-icon>
Sync
</button>
</div>
<!-- Filtres selon le type -->
<div *ngIf="canSelectMerchant() && shouldShowMerchantSelector()" class="filters-card">
<div class="d-flex align-items-center gap-2">
<!-- Filtre Merchant pour hub users -->
<div class="input-group input-group-sm">
<span class="input-group-text bg-light">
<ng-icon name="lucideStore"></ng-icon>
</span>
<select class="form-control form-control-sm"
[ngModel]="merchantId"
(ngModelChange)="selectMerchant($event)"
style="width: 180px;">
<option [value]="undefined">
<ng-icon name="lucideGlobe" class="me-1"></ng-icon>
Données globales
</option>
<option *ngFor="let merchant of allowedMerchants" [value]="merchant.id">
{{ merchant.name }} (ID: {{ merchant.id }})
</option>
</select>
</div>
<!-- Badge de contexte -->
<div class="badge" [ngClass]="isViewingGlobal() ? 'bg-info' : 'bg-success'">
<ng-icon [name]="isViewingGlobal() ? 'lucideGlobe' : 'lucideStore'" class="me-1"></ng-icon>
{{ getCurrentMerchantName() }}
</div>
</div>
</div>
<!-- Options dropdown seulement pour hub users admin -->
<div *ngIf="access.isHubUser && canManageMerchants()" ngbDropdown class="dropdown">
<!-- Bouton déclencheur -->
<button class="btn btn-outline-secondary dropdown-toggle"
ngbDropdownToggle
type="button">
<ng-icon name="lucideSettings" class="me-2"></ng-icon>
Options
</button>
<!-- Menu déroulant -->
<div class="dropdown-menu dropdown-menu-end" ngbDropdownMenu>
<button ngbDropdownItem (click)="checkSystemHealth()">
<ng-icon name="lucideHeartPulse" class="me-2"></ng-icon>
Vérifier la santé
</button>
<div class="dropdown-divider"></div>
<button ngbDropdownItem (click)="refreshData()">
<ng-icon name="lucideRefreshCw" class="me-2"></ng-icon>
Rafraîchir tout
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Barre d'état rapide -->
<div class="status-bar mb-4">
<div class="row g-2">
<!-- Info rôle -->
<div class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon [name]="getRoleStatusIcon()" [class]="getRoleStatusColor()"></ng-icon>
<small>{{ currentRoleLabel }}</small>
</div>
</div>
<!-- Mode d'affichage -->
<div class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon [name]="isViewingGlobal() ? 'lucideGlobe' : 'lucideStore'"
[class]="isViewingGlobal() ? 'text-info' : 'text-success'"></ng-icon>
<small>{{ getCurrentMerchantName() }}</small>
</div>
</div>
<div class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideClock" class="text-primary"></ng-icon>
<small>Mis à jour: {{ lastUpdated | date:'HH:mm:ss' }}</small>
</div>
</div>
<!-- Services en ligne seulement pour hub users -->
<div *ngIf="shouldShowSystemHealth()" class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideCpu" class="text-success"></ng-icon>
<small>Services: {{ stats.onlineServices }}/{{ stats.totalServices }} en ligne</small>
</div>
</div>
<!-- Info merchant pour merchant users -->
<div *ngIf="access.isMerchantUser" class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideStore" class="text-success"></ng-icon>
<small>Merchant ID: {{ merchantId }}</small>
</div>
</div>
<!-- Merchant sélectionné pour hub users -->
<div *ngIf="access.isHubUser && isViewingMerchant()" class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideStore" class="text-info"></ng-icon>
<small>Merchant ID: {{ merchantId }}</small>
</div>
</div>
</div>
</div>
<!-- Message d'erreur si pas de permissions -->
<div *ngIf="!shouldShowTransactions()"
class="alert alert-warning mb-4">
<div class="d-flex align-items-center">
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
<div class="flex-grow-1">
<strong>Permissions insuffisantes</strong>
<div class="text-muted small">Vous n'avez pas les permissions nécessaires pour voir les données.</div>
</div>
</div>
</div>
<!-- Message de sync seulement si autorisé à voir les alertes -->
<div *ngIf="syncResponse && shouldShowAlerts()" class="alert alert-success alert-dismissible fade show mb-4">
<div class="d-flex align-items-center">
<ng-icon name="lucideCheckCircle2" class="me-2 fs-5"></ng-icon>
<div class="flex-grow-1">
<strong>{{ syncResponse.message }}</strong>
<div class="text-muted small">Synchronisée à {{ formatDate(syncResponse.timestamp) }}</div>
</div>
<button type="button" class="btn-close" (click)="syncResponse = null"></button>
</div>
</div>
<!-- ==================== SECTION DES KPIs HORIZONTAUX ==================== -->
<div *ngIf="shouldShowKPIs()" class="kpi-section mb-4">
<div class="row g-3">
<!-- Transactions Journalières -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-primary border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Transactions</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getPaymentStats().daily.transactions) }}</h4>
<small class="text-muted">Journalier</small>
</div>
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideStore" class="text-primary fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatCurrency(getPaymentStats().daily.revenue) }}</span>
<span class="badge bg-primary bg-opacity-25 text-primary">
<ng-icon name="lucideArrowUpRight" class="me-1"></ng-icon>
{{ getPaymentStats().daily.successRate | number:'1.0-0' }}%
</span>
</div>
</div>
</div>
</div>
<!-- Transactions Hebdomadaires -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-info border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Transactions</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getPaymentStats().weekly.transactions) }}</h4>
<small class="text-muted">Hebdomadaire</small>
</div>
<div class="avatar-sm bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideCalendar" class="text-info fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatCurrency(getPaymentStats().weekly.revenue) }}</span>
<span class="badge bg-info bg-opacity-25 text-info">
<ng-icon name="lucideArrowUpRight" class="me-1"></ng-icon>
{{ getPaymentStats().weekly.successRate | number:'1.0-0' }}%
</span>
</div>
</div>
</div>
</div>
<!-- Transactions Mensuel -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-info border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Transactions</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getPaymentStats().monthly.transactions) }}</h4>
<small class="text-muted">Mensuel</small>
</div>
<div class="avatar-sm bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideCalendar" class="text-info fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatCurrency(getPaymentStats().monthly.revenue) }}</span>
<span class="badge bg-info bg-opacity-25 text-info">
<ng-icon name="lucideArrowUpRight" class="me-1"></ng-icon>
{{ getPaymentStats().monthly.successRate | number:'1.0-0' }}%
</span>
</div>
</div>
</div>
</div>
<!-- Revenue Annuel -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-purple border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Revenue {{ currentYear }}</h6>
<h4 class="fw-bold mb-0">{{ formatCurrency(stats.yearlyRevenue) }}</h4>
<small class="text-muted">Annuel</small>
</div>
<div class="avatar-sm bg-purple bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideTrophy" class="text-purple fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatNumber(stats.yearlyTransactions) }} transactions</span>
<span class="badge bg-purple bg-opacity-25 text-purple">
<ng-icon name="lucideCalendar" class="me-1"></ng-icon>
{{ currentYear }}
</span>
</div>
</div>
</div>
</div>
<!-- Abonnements Actifs -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-warning border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Abonnements</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getSubscriptionStats().active) }}</h4>
<small class="text-muted">Actifs</small>
</div>
<div class="avatar-sm bg-warning bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideUsers" class="text-warning fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">Total: {{ formatNumber(getSubscriptionStats().total) }}</span>
<span class="badge bg-warning bg-opacity-25 text-warning">
<ng-icon name="lucidePlus" class="me-1"></ng-icon>
+{{ getSubscriptionStats().newToday }}
</span>
</div>
</div>
</div>
</div>
<!-- Taux de Succès -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-danger border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Taux de succès</h6>
<h4 class="fw-bold mb-0">{{ stats.successRate | number:'1.1-1' }}%</h4>
<small class="text-muted">Global</small>
</div>
<div class="avatar-sm bg-danger bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideCheckCircle2" class="text-danger fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ getPerformanceLabel(stats.successRate) }}</span>
<span class="badge" [ngClass]="getSuccessRateClass(stats.successRate)">
<ng-icon name="lucideTrendingUp" class="me-1"></ng-icon>
{{ stats.avgSuccessRate | number:'1.0-0' }}% cible
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== SECTION DES GRAPHIQUES FLEXIBLES ==================== -->
<div *ngIf="shouldShowCharts() && !loading.globalData && !loading.merchantData" class="charts-section mb-4">
<div class="row g-4">
<!-- Graphique principal dynamique -->
<div class="col-xl-8">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon [name]="getMetricIcon(dataSelection.metric)" class="text-primary me-2"></ng-icon>
{{ getChartTitle(dataSelection.metric) }}
<span *ngIf="isViewingMerchant()" class="badge bg-success ms-2">
<ng-icon name="lucideStore" class="me-1"></ng-icon>
Merchant {{ merchantId }}
</span>
<span *ngIf="isViewingGlobal()" class="badge bg-info ms-2">
<ng-icon name="lucideGlobe" class="me-1"></ng-icon>
Données globales
</span>
</h5>
<p class="text-muted small mb-0">Visualisation en temps réel</p>
</div>
<div class="d-flex gap-2 align-items-center">
<!-- Sélection de métrique -->
<div class="dropdown" ngbDropdown>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button"
ngbDropdownToggle>
<ng-icon [name]="getMetricIcon(dataSelection.metric)" class="me-1"></ng-icon>
{{ getCurrentMetricLabel() }}
</button>
<div class="dropdown-menu dropdown-menu-end" ngbDropdownMenu>
<a *ngFor="let metric of availableMetrics"
class="dropdown-item d-flex align-items-center"
href="javascript:void(0)"
(click)="changeMetric(metric.id)">
<ng-icon [name]="metric.icon" class="me-2"></ng-icon>
{{ metric.label }}
</a>
</div>
</div>
<!-- Sélection de type de graphique -->
<div class="btn-group btn-group-sm" role="group">
<button *ngFor="let type of availableChartTypes"
type="button"
class="btn btn-outline-secondary"
[class.active]="dataSelection.chartType === type"
(click)="changeChartType(type)"
title="{{ type === 'line' ? 'Courbe' : type === 'bar' ? 'Barres' : type === 'pie' ? 'Camembert' : 'Anneau' }}">
<ng-icon [name]="type === 'line' ? 'lucideTrendingUp' : type === 'bar' ? 'lucideBarChart3' : 'lucidePieChart'"></ng-icon>
</button>
</div>
<button class="btn btn-sm btn-outline-primary"
(click)="refreshChartData()"
[disabled]="loading.chart">
<ng-icon name="lucideRefreshCw" [class.spin]="loading.chart" class="me-1"></ng-icon>
Rafraîchir
</button>
</div>
</div>
</div>
<div class="card-body pt-0">
<div *ngIf="loading.chart" class="text-center py-5">
<div class="spinner-border text-primary"></div>
<p class="mt-2 text-muted">Chargement du graphique...</p>
</div>
<div *ngIf="!loading.chart && !getCurrentTransactionData()"
class="text-center py-5 text-muted">
<ng-icon name="lucideBarChart3" class="fs-1 opacity-25"></ng-icon>
<p class="mt-2">Aucune donnée disponible</p>
<button class="btn btn-sm btn-outline-primary mt-2"
(click)="refreshData()">
Charger les données
</button>
</div>
<div *ngIf="!loading.chart && getCurrentTransactionData()"
class="position-relative" style="height: 300px;">
<canvas #mainChartCanvas></canvas>
</div>
</div>
</div>
</div>
<!-- Panneau droit avec 2 mini-graphiques -->
<div class="col-xl-4">
<div class="row h-100 g-4">
<!-- Graphique de comparaison seulement pour hub users en mode global -->
<div *ngIf="shouldShowChart('comparison')" class="col-12">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
<ng-icon name="lucideBarChart3" class="text-info me-2"></ng-icon>
Comparaison Hebdo/Mens
</h5>
<small class="text-muted">Dernières 8 périodes</small>
</div>
</div>
<div class="card-body pt-0">
<div *ngIf="!weeklyTransactions || !monthlyTransactions"
class="text-center py-4 text-muted">
<ng-icon name="lucideBarChart3" class="fs-1 opacity-25 mb-2 d-block"></ng-icon>
<small>Données de comparaison indisponibles</small>
</div>
<div *ngIf="weeklyTransactions && monthlyTransactions"
class="position-relative" style="height: 180px;">
<canvas #comparisonChartCanvas></canvas>
</div>
</div>
</div>
</div>
<!-- Taux de succès circulaire -->
<div *ngIf="shouldShowChart('successRate')" class="col-12">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
<ng-icon name="lucideActivity" class="text-success me-2"></ng-icon>
Performance
</h5>
<button class="btn btn-sm btn-outline-success btn-sm"
(click)="refreshData()"
[disabled]="loading.globalData || loading.merchantData">
<ng-icon name="lucideRefreshCw"
[class.spin]="loading.globalData || loading.merchantData"></ng-icon>
</button>
</div>
</div>
<div class="card-body">
<div class="text-center mb-3">
<div class="position-relative d-inline-block">
<canvas #successRateChartCanvas width="140" height="140"></canvas>
<div class="position-absolute top-50 start-50 translate-middle">
<h3 class="fw-bold mb-0" [ngClass]="getSuccessRateClass(stats.successRate)">
{{ stats.successRate | number:'1.0-0' }}%
</h3>
<small class="text-muted">Taux de succès</small>
</div>
</div>
</div>
<div class="row text-center g-2">
<div class="col-4">
<div class="p-2 border rounded bg-success bg-opacity-10">
<div class="text-success fw-bold">
{{ formatNumber(getCurrentTransactionData()?.items?.[0]?.successCount || 0) }}
</div>
<small class="text-muted">Réussies</small>
</div>
</div>
<div class="col-4">
<div class="p-2 border rounded bg-danger bg-opacity-10">
<div class="text-danger fw-bold">
{{ formatNumber(getCurrentTransactionData()?.items?.[0]?.failedCount || 0) }}
</div>
<small class="text-muted">Échouées</small>
</div>
</div>
<div class="col-4">
<div class="p-2 border rounded bg-info bg-opacity-10">
<div class="text-info fw-bold">
{{ formatNumber(getCurrentTransactionData()?.items?.[0]?.pendingCount || 0) }}
</div>
<small class="text-muted">En attente</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== SECTION SANTÉ DES APIS ==================== -->
<div *ngIf="shouldShowSystemHealth()" class="health-section mb-4">
<div class="card">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon name="lucideServer" class="text-dark me-2"></ng-icon>
Santé des APIs DCB
</h5>
<p class="text-muted small mb-0">Statut en temps réel des services backend</p>
</div>
<div class="d-flex gap-2 align-items-center">
<div class="alert" [ngClass]="'alert-' + overallHealth.color">
<div class="d-flex align-items-center">
<ng-icon [name]="overallHealth.icon" class="me-2"></ng-icon>
<small class="fw-medium">{{ overallHealth.message }}</small>
</div>
</div>
<button class="btn btn-sm btn-outline-danger"
(click)="checkSystemHealth()"
[disabled]="loading.healthCheck">
<ng-icon name="lucideRefreshCw" [class.spin]="loading.healthCheck"></ng-icon>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div *ngFor="let service of systemHealth" class="col-xl-3 col-md-6">
<div class="card h-100" [ngClass]="'border-' + service.color">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<h6 class="mb-0">{{ service.service }}</h6>
<small class="text-muted">{{ service.url }}</small>
</div>
<div class="d-flex flex-column align-items-end">
<span class="badge mb-1" [ngClass]="'bg-' + service.color">
{{ service.status }}
</span>
<small class="text-muted" [ngClass]="getStatusCodeClass(service.statusCode)">
{{ service.statusCode }}
</small>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mt-3">
<div>
<small class="text-muted">
<ng-icon name="lucideClock" class="me-1"></ng-icon>
{{ formatTimeAgo(service.checkedAt) }}
</small>
</div>
<div *ngIf="service.responseTime">
<small class="text-muted" [ngClass]="getResponseTimeClass(service.responseTime)">
{{ service.responseTime }}
</small>
</div>
</div>
<div *ngIf="service.error" class="mt-2">
<small class="text-danger">
<ng-icon name="lucideAlertTriangle" class="me-1"></ng-icon>
{{ service.error }}
</small>
</div>
</div>
</div>
</div>
</div>
<div class="mt-3 text-center">
<small class="text-muted">
<ng-icon name="lucideInfo" class="me-1"></ng-icon>
Vérification automatique toutes les 5 minutes
</small>
</div>
</div>
</div>
</div>
<!-- ==================== SECTION DES TABLEAUX ==================== -->
<div *ngIf="shouldShowTransactions()" class="tables-section">
<div class="row g-4">
<!-- Transactions récentes -->
<div class="col-xl-6">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon name="lucideListChecks" class="text-primary me-2"></ng-icon>
Transactions récentes
<span class="badge ms-2"
[ngClass]="isViewingGlobal() ? 'bg-info' : 'bg-success'">
{{ getCurrentMerchantName() }}
</span>
</h5>
<p class="text-muted small mb-0">Dernières 24 heures</p>
</div>
<div>
<button class="btn btn-sm btn-outline-primary"
(click)="refreshData()"
[disabled]="loading.globalData || loading.merchantData">
<ng-icon name="lucideRefreshCw"
[class.spin]="loading.globalData || loading.merchantData"></ng-icon>
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="ps-3">Période</th>
<th class="text-end">Montant</th>
<th class="text-end">Transactions</th>
<th class="text-end pe-3">Succès</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of (getCurrentTransactionData()?.items?.slice(0, 5) || [])">
<td class="ps-3">
<div>{{ item.period }}</div>
<small class="text-muted">{{ isViewingGlobal() ? 'Tous merchants' : 'Merchant ' + merchantId }}</small>
</td>
<td class="text-end">
<div class="fw-medium">{{ formatCurrency(item.totalAmount) }}</div>
</td>
<td class="text-end">
<div>{{ formatNumber(item.count) }}</div>
</td>
<td class="text-end pe-3">
<span [ngClass]="getSuccessRateClass((item.count > 0 ? (item.successCount / item.count) * 100 : 0))">
{{ (item.count > 0 ? (item.successCount / item.count) * 100 : 0) | number:'1.1-1' }}%
</span>
</td>
</tr>
<tr *ngIf="!getCurrentTransactionData()?.items?.length">
<td colspan="4" class="text-center text-muted py-5">
<ng-icon name="lucideDatabase" class="fs-1 opacity-25 mb-3 d-block"></ng-icon>
<p class="mb-0">Aucune transaction disponible</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer bg-transparent border-top-0">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<ng-icon name="lucideInfo" class="me-1"></ng-icon>
{{ getCurrentTransactionData()?.items?.length || 0 }} périodes au total
</small>
<a href="#" class="btn btn-sm btn-outline-primary">Voir tout</a>
</div>
</div>
</div>
</div>
<!-- Alertes système - Masqué si pas autorisé -->
<div *ngIf="shouldShowAlerts()" class="col-xl-6">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon name="lucideBell" class="text-warning me-2"></ng-icon>
Alertes système
</h5>
<p class="text-muted small mb-0">Notifications en temps réel</p>
</div>
<div>
<span class="badge" [ngClass]="getAlertBadgeClass()">
{{ alerts.length }}
</span>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="alert-list">
<div *ngFor="let alert of alerts.slice(0, 5)" class="alert-item p-3 border-bottom">
<div class="d-flex align-items-start">
<div class="me-3">
<ng-icon *ngIf="alert.type === 'warning'"
name="lucideAlertTriangle"
class="text-warning fs-5"></ng-icon>
<ng-icon *ngIf="alert.type === 'info'"
name="lucideInfo"
class="text-info fs-5"></ng-icon>
<ng-icon *ngIf="alert.type === 'success'"
name="lucideCheckCircle2"
class="text-success fs-5"></ng-icon>
<ng-icon *ngIf="alert.type === 'danger'"
name="lucideAlertCircle"
class="text-danger fs-5"></ng-icon>
</div>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-start mb-1">
<h6 class="mb-0">{{ alert.title }}</h6>
<small class="text-muted">{{ formatTimeAgo(alert.timestamp) }}</small>
</div>
<p class="mb-0 text-muted small">{{ alert.description }}</p>
</div>
</div>
</div>
<div *ngIf="alerts.length === 0" class="text-center text-muted py-5">
<ng-icon name="lucideCheckCircle2" class="text-success fs-1 opacity-25 mb-3 d-block"></ng-icon>
<p class="mb-0">Aucune alerte active</p>
<small class="text-muted">Tous les systèmes fonctionnent normalement</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent border-top-0">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<ng-icon name="lucideClock" class="me-1"></ng-icon>
Dernière vérification: {{ formatTimeAgo(alerts[0]?.timestamp) || 'Jamais' }}
</small>
<button class="btn btn-sm btn-outline-warning" (click)="checkSystemHealth()">
<ng-icon name="lucideRefreshCw" class="me-1"></ng-icon>
Vérifier
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="dashboard-footer mt-4">
<div class="card bg-light">
<div class="card-body py-2">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted small">
<ng-icon name="lucideCode" class="me-1"></ng-icon>
Dashboard FinTech v2.0 •
<ng-icon [name]="currentRoleIcon" class="me-1 ms-2"></ng-icon>
{{ currentRoleLabel }}
</div>
<div class="text-muted small">
<ng-icon [name]="isViewingGlobal() ? 'lucideGlobe' : 'lucideStore'" class="me-1"></ng-icon>
{{ getCurrentMerchantName() }}
</div>
<div class="text-muted small">
<ng-icon name="lucideClock" class="me-1"></ng-icon>
Mise à jour: {{ lastUpdated | date:'dd/MM/yyyy HH:mm:ss' }}
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,13 +0,0 @@
<div class="container-fluid">
<app-page-title
title="Payment Aggregation Hub"
subTitle="Monitoring en temps réel des paiements mobiles DCB"
[badge]="{icon: 'lucideSmartphone', text: 'Direct Carrier Billing'}"
/>
<div class="row g-4 mb-4">
<div class="col-12">
<app-dcb-reporting-dashboard></app-dcb-reporting-dashboard>
</div>
</div>
</div>

View File

@ -1,22 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DcbDashboard } from './dcb-dashboard'
describe('Dashboard', () => {
let component: DcbDashboard
let fixture: ComponentFixture<DcbDashboard>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DcbDashboard],
}).compileComponents()
fixture = TestBed.createComponent(DcbDashboard)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@ -1,15 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageTitle } from '@app/components/page-title/page-title';
import { DcbReportingDashboard } from './components/dcb-reporting-dashboard';
@Component({
selector: 'app-dcb-dashboard',
imports: [
CommonModule,
PageTitle,
DcbReportingDashboard,
],
templateUrl: './dcb-dashboard.html',
})
export class DcbDashboard {}

View File

@ -0,0 +1,785 @@
<div class="container-fluid">
<app-page-title
title="Payment Aggregation Hub"
subTitle="Monitoring en temps réel des paiements mobiles DCB"
[badge]="{icon: 'lucideSmartphone', text: 'Direct Carrier Billing'}"
/>
<div class="row g-4 mb-4">
<div class="col-12">
<div class="container-fluid dashboard-container">
<!-- Header avec navigation -->
<div class="dashboard-header">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0 text-primary">
<ng-icon name="lucideLayoutDashboard" class="me-2"></ng-icon>
Dashboard FinTech Reporting
<span *ngIf="access.isMerchantUser" class="badge bg-success ms-2">
<ng-icon name="lucideStore" class="me-1"></ng-icon>
<div *ngIf="isValidMerchantId(merchantId)">
Merchant ID: {{ merchantId }}
</div>
<div *ngIf="!isValidMerchantId(merchantId)">
Aucun merchant sélectionné
</div>
</span>
<span *ngIf="access.isHubUser" class="badge bg-primary ms-2">
<ng-icon name="lucideShield" class="me-1"></ng-icon>
Hub Admin
</span>
</h1>
<p class="text-muted mb-0">
<ng-icon [name]="currentRoleIcon" class="me-1"></ng-icon>
{{ currentRoleLabel }} - {{ getCurrentMerchantName() }}
</p>
</div>
<div class="d-flex gap-2 align-items-center">
<!-- Contrôles rapides -->
<div class="btn-group btn-group-sm" role="group">
<!-- Bouton Actualiser selon le type -->
<button class="btn btn-outline-primary btn-sm"
(click)="refreshData()"
[disabled]="loading.globalData || loading.merchantData">
<ng-icon name="lucideRefreshCw"
[class.spin]="loading.globalData || loading.merchantData"
class="me-1"></ng-icon>
{{ (loading.globalData || loading.merchantData) ? 'Chargement...' : 'Actualiser' }}
</button>
<!-- Bouton Sync seulement si autorisé -->
<button *ngIf="canTriggerSync()"
class="btn btn-outline-danger btn-sm"
(click)="triggerSync()"
[disabled]="loading.sync">
<ng-icon name="lucideRefreshCcw" [class.spin]="loading.sync" class="me-1"></ng-icon>
Sync
</button>
</div>
<!-- Filtres selon le type -->
<div *ngIf="canSelectMerchant() && shouldShowMerchantSelector()" class="filters-card">
<div class="d-flex align-items-center gap-2">
<!-- Filtre Merchant pour hub users -->
<div class="input-group input-group-sm">
<span class="input-group-text bg-light">
<ng-icon name="lucideStore"></ng-icon>
</span>
<select class="form-control form-control-sm"
[ngModel]="merchantId"
(ngModelChange)="selectMerchant($event)"
style="width: 180px;">
<option [value]="undefined">
<ng-icon name="lucideGlobe" class="me-1"></ng-icon>
Données globales
</option>
<option *ngFor="let merchant of allowedMerchants" [value]="merchant.id">
{{ merchant.name }} (ID: {{ merchant.id }})
</option>
</select>
</div>
<!-- Badge de contexte -->
<div class="badge" [ngClass]="isViewingGlobal() ? 'bg-info' : 'bg-success'">
<ng-icon [name]="isViewingGlobal() ? 'lucideGlobe' : 'lucideStore'" class="me-1"></ng-icon>
{{ getCurrentMerchantName() }}
</div>
</div>
</div>
<!-- Options dropdown seulement pour hub users admin -->
<div *ngIf="access.isHubUser && canManageMerchants()" ngbDropdown class="dropdown">
<!-- Bouton déclencheur -->
<button class="btn btn-outline-secondary dropdown-toggle"
ngbDropdownToggle
type="button">
<ng-icon name="lucideSettings" class="me-2"></ng-icon>
Options
</button>
<!-- Menu déroulant -->
<div class="dropdown-menu dropdown-menu-end" ngbDropdownMenu>
<button ngbDropdownItem (click)="checkSystemHealth()">
<ng-icon name="lucideHeartPulse" class="me-2"></ng-icon>
Vérifier la santé
</button>
<div class="dropdown-divider"></div>
<button ngbDropdownItem (click)="refreshData()">
<ng-icon name="lucideRefreshCw" class="me-2"></ng-icon>
Rafraîchir tout
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Barre d'état rapide -->
<div class="status-bar mb-4">
<div class="row g-2">
<!-- Info rôle -->
<div class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon [name]="getRoleStatusIcon()" [class]="getRoleStatusColor()"></ng-icon>
<small>{{ currentRoleLabel }}</small>
</div>
</div>
<!-- Mode d'affichage -->
<div class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon [name]="isViewingGlobal() ? 'lucideGlobe' : 'lucideStore'"
[class]="isViewingGlobal() ? 'text-info' : 'text-success'"></ng-icon>
<small>{{ getCurrentMerchantName() }}</small>
</div>
</div>
<div class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideClock" class="text-primary"></ng-icon>
<small>Mis à jour: {{ lastUpdated | date:'HH:mm:ss' }}</small>
</div>
</div>
<!-- Services en ligne seulement pour hub users -->
<div *ngIf="shouldShowSystemHealth()" class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideCpu" class="text-success"></ng-icon>
<small>Services: {{ stats.onlineServices }}/{{ stats.totalServices }} en ligne</small>
</div>
</div>
<!-- Info merchant pour merchant users -->
<div *ngIf="access.isMerchantUser" class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideStore" class="text-success"></ng-icon>
<small>Merchant ID: {{ merchantId }}</small>
</div>
</div>
<!-- Merchant sélectionné pour hub users -->
<div *ngIf="access.isHubUser && isViewingMerchant()" class="col-auto">
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded">
<ng-icon name="lucideStore" class="text-info"></ng-icon>
<small>Merchant ID: {{ merchantId }}</small>
</div>
</div>
</div>
</div>
<!-- Message d'erreur si pas de permissions -->
<div *ngIf="!shouldShowTransactions()"
class="alert alert-warning mb-4">
<div class="d-flex align-items-center">
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
<div class="flex-grow-1">
<strong>Permissions insuffisantes</strong>
<div class="text-muted small">Vous n'avez pas les permissions nécessaires pour voir les données.</div>
</div>
</div>
</div>
<!-- Message de sync seulement si autorisé à voir les alertes -->
<div *ngIf="syncResponse && shouldShowAlerts()" class="alert alert-success alert-dismissible fade show mb-4">
<div class="d-flex align-items-center">
<ng-icon name="lucideCheckCircle2" class="me-2 fs-5"></ng-icon>
<div class="flex-grow-1">
<strong>{{ syncResponse.message }}</strong>
<div class="text-muted small">Synchronisée à {{ formatDate(syncResponse.timestamp) }}</div>
</div>
<button type="button" class="btn-close" (click)="syncResponse = null"></button>
</div>
</div>
<!-- ==================== SECTION DES KPIs HORIZONTAUX ==================== -->
<div *ngIf="shouldShowKPIs()" class="kpi-section mb-4">
<div class="row g-3">
<!-- Transactions Journalières -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-primary border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Transactions</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getPaymentStats().daily.transactions) }}</h4>
<small class="text-muted">Journalier</small>
</div>
<div class="avatar-sm bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideStore" class="text-primary fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatCurrency(getPaymentStats().daily.revenue) }}</span>
<span class="badge bg-primary bg-opacity-25 text-primary">
<ng-icon name="lucideArrowUpRight" class="me-1"></ng-icon>
{{ getPaymentStats().daily.successRate | number:'1.0-0' }}%
</span>
</div>
</div>
</div>
</div>
<!-- Transactions Hebdomadaires -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-info border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Transactions</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getPaymentStats().weekly.transactions) }}</h4>
<small class="text-muted">Hebdomadaire</small>
</div>
<div class="avatar-sm bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideCalendar" class="text-info fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatCurrency(getPaymentStats().weekly.revenue) }}</span>
<span class="badge bg-info bg-opacity-25 text-info">
<ng-icon name="lucideArrowUpRight" class="me-1"></ng-icon>
{{ getPaymentStats().weekly.successRate | number:'1.0-0' }}%
</span>
</div>
</div>
</div>
</div>
<!-- Transactions Mensuel -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-info border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Transactions</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getPaymentStats().monthly.transactions) }}</h4>
<small class="text-muted">Mensuel</small>
</div>
<div class="avatar-sm bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideCalendar" class="text-info fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatCurrency(getPaymentStats().monthly.revenue) }}</span>
<span class="badge bg-info bg-opacity-25 text-info">
<ng-icon name="lucideArrowUpRight" class="me-1"></ng-icon>
{{ getPaymentStats().monthly.successRate | number:'1.0-0' }}%
</span>
</div>
</div>
</div>
</div>
<!-- Revenue Annuel -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-purple border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Revenue {{ currentYear }}</h6>
<h4 class="fw-bold mb-0">{{ formatCurrency(stats.yearlyRevenue) }}</h4>
<small class="text-muted">Annuel</small>
</div>
<div class="avatar-sm bg-purple bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideTrophy" class="text-purple fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ formatNumber(stats.yearlyTransactions) }} transactions</span>
<span class="badge bg-purple bg-opacity-25 text-purple">
<ng-icon name="lucideCalendar" class="me-1"></ng-icon>
{{ currentYear }}
</span>
</div>
</div>
</div>
</div>
<!-- Abonnements Actifs -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-warning border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Abonnements</h6>
<h4 class="fw-bold mb-0">{{ formatNumber(getSubscriptionStats().active) }}</h4>
<small class="text-muted">Actifs</small>
</div>
<div class="avatar-sm bg-warning bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideUsers" class="text-warning fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">Total: {{ formatNumber(getSubscriptionStats().total) }}</span>
<span class="badge bg-warning bg-opacity-25 text-warning">
<ng-icon name="lucidePlus" class="me-1"></ng-icon>
+{{ getSubscriptionStats().newToday }}
</span>
</div>
</div>
</div>
</div>
<!-- Taux de Succès -->
<div class="col-xl-2 col-md-4 col-sm-6">
<div class="card kpi-card border-start border-danger border-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 class="text-uppercase text-muted mb-1">Taux de succès</h6>
<h4 class="fw-bold mb-0">{{ stats.successRate | number:'1.1-1' }}%</h4>
<small class="text-muted">Global</small>
</div>
<div class="avatar-sm bg-danger bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
<ng-icon name="lucideCheckCircle2" class="text-danger fs-5"></ng-icon>
</div>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small">{{ getPerformanceLabel(stats.successRate) }}</span>
<span class="badge" [ngClass]="getSuccessRateClass(stats.successRate)">
<ng-icon name="lucideTrendingUp" class="me-1"></ng-icon>
{{ stats.avgSuccessRate | number:'1.0-0' }}% cible
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== SECTION DES GRAPHIQUES FLEXIBLES ==================== -->
<div *ngIf="shouldShowCharts() && !loading.globalData && !loading.merchantData" class="charts-section mb-4">
<div class="row g-4">
<!-- Graphique principal dynamique -->
<div class="col-xl-8">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon [name]="getMetricIcon(dataSelection.metric)" class="text-primary me-2"></ng-icon>
{{ getChartTitle(dataSelection.metric) }}
<span *ngIf="isViewingMerchant()" class="badge bg-success ms-2">
<ng-icon name="lucideStore" class="me-1"></ng-icon>
Merchant {{ merchantId }}
</span>
<span *ngIf="isViewingGlobal()" class="badge bg-info ms-2">
<ng-icon name="lucideGlobe" class="me-1"></ng-icon>
Données globales
</span>
</h5>
<p class="text-muted small mb-0">Visualisation en temps réel</p>
</div>
<div class="d-flex gap-2 align-items-center">
<!-- Sélection de métrique -->
<div class="dropdown" ngbDropdown>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button"
ngbDropdownToggle>
<ng-icon [name]="getMetricIcon(dataSelection.metric)" class="me-1"></ng-icon>
{{ getCurrentMetricLabel() }}
</button>
<div class="dropdown-menu dropdown-menu-end" ngbDropdownMenu>
<a *ngFor="let metric of availableMetrics"
class="dropdown-item d-flex align-items-center"
href="javascript:void(0)"
(click)="changeMetric(metric.id)">
<ng-icon [name]="metric.icon" class="me-2"></ng-icon>
{{ metric.label }}
</a>
</div>
</div>
<!-- Sélection de type de graphique -->
<div class="btn-group btn-group-sm" role="group">
<button *ngFor="let type of availableChartTypes"
type="button"
class="btn btn-outline-secondary"
[class.active]="dataSelection.chartType === type"
(click)="changeChartType(type)"
title="{{ type === 'line' ? 'Courbe' : type === 'bar' ? 'Barres' : type === 'pie' ? 'Camembert' : 'Anneau' }}">
<ng-icon [name]="type === 'line' ? 'lucideTrendingUp' : type === 'bar' ? 'lucideBarChart3' : 'lucidePieChart'"></ng-icon>
</button>
</div>
<button class="btn btn-sm btn-outline-primary"
(click)="refreshChartData()"
[disabled]="loading.chart">
<ng-icon name="lucideRefreshCw" [class.spin]="loading.chart" class="me-1"></ng-icon>
Rafraîchir
</button>
</div>
</div>
</div>
<div class="card-body pt-0">
<div *ngIf="loading.chart" class="text-center py-5">
<div class="spinner-border text-primary"></div>
<p class="mt-2 text-muted">Chargement du graphique...</p>
</div>
<div *ngIf="!loading.chart && !getCurrentTransactionData()"
class="text-center py-5 text-muted">
<ng-icon name="lucideBarChart3" class="fs-1 opacity-25"></ng-icon>
<p class="mt-2">Aucune donnée disponible</p>
<button class="btn btn-sm btn-outline-primary mt-2"
(click)="refreshData()">
Charger les données
</button>
</div>
<div *ngIf="!loading.chart && getCurrentTransactionData()"
class="position-relative" style="height: 300px;">
<canvas #mainChartCanvas></canvas>
</div>
</div>
</div>
</div>
<!-- Panneau droit avec 2 mini-graphiques -->
<div class="col-xl-4">
<div class="row h-100 g-4">
<!-- Graphique de comparaison seulement pour hub users en mode global -->
<div *ngIf="shouldShowChart('comparison')" class="col-12">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
<ng-icon name="lucideBarChart3" class="text-info me-2"></ng-icon>
Comparaison Hebdo/Mens
</h5>
<small class="text-muted">Dernières 8 périodes</small>
</div>
</div>
<div class="card-body pt-0">
<div *ngIf="!weeklyTransactions || !monthlyTransactions"
class="text-center py-4 text-muted">
<ng-icon name="lucideBarChart3" class="fs-1 opacity-25 mb-2 d-block"></ng-icon>
<small>Données de comparaison indisponibles</small>
</div>
<div *ngIf="weeklyTransactions && monthlyTransactions"
class="position-relative" style="height: 180px;">
<canvas #comparisonChartCanvas></canvas>
</div>
</div>
</div>
</div>
<!-- Taux de succès circulaire -->
<div *ngIf="shouldShowChart('successRate')" class="col-12">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
<ng-icon name="lucideActivity" class="text-success me-2"></ng-icon>
Performance
</h5>
<button class="btn btn-sm btn-outline-success btn-sm"
(click)="refreshData()"
[disabled]="loading.globalData || loading.merchantData">
<ng-icon name="lucideRefreshCw"
[class.spin]="loading.globalData || loading.merchantData"></ng-icon>
</button>
</div>
</div>
<div class="card-body">
<div class="text-center mb-3">
<div class="position-relative d-inline-block">
<canvas #successRateChartCanvas width="140" height="140"></canvas>
<div class="position-absolute top-50 start-50 translate-middle">
<h3 class="fw-bold mb-0" [ngClass]="getSuccessRateClass(stats.successRate)">
{{ stats.successRate | number:'1.0-0' }}%
</h3>
<small class="text-muted">Taux de succès</small>
</div>
</div>
</div>
<div class="row text-center g-2">
<div class="col-4">
<div class="p-2 border rounded bg-success bg-opacity-10">
<div class="text-success fw-bold">
{{ formatNumber(getCurrentTransactionData()?.items?.[0]?.successCount || 0) }}
</div>
<small class="text-muted">Réussies</small>
</div>
</div>
<div class="col-4">
<div class="p-2 border rounded bg-danger bg-opacity-10">
<div class="text-danger fw-bold">
{{ formatNumber(getCurrentTransactionData()?.items?.[0]?.failedCount || 0) }}
</div>
<small class="text-muted">Échouées</small>
</div>
</div>
<div class="col-4">
<div class="p-2 border rounded bg-info bg-opacity-10">
<div class="text-info fw-bold">
{{ formatNumber(getCurrentTransactionData()?.items?.[0]?.pendingCount || 0) }}
</div>
<small class="text-muted">En attente</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== SECTION SANTÉ DES APIS ==================== -->
<div *ngIf="shouldShowSystemHealth()" class="health-section mb-4">
<div class="card">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon name="lucideServer" class="text-dark me-2"></ng-icon>
Santé des APIs DCB
</h5>
<p class="text-muted small mb-0">Statut en temps réel des services backend</p>
</div>
<div class="d-flex gap-2 align-items-center">
<div class="alert" [ngClass]="'alert-' + overallHealth.color">
<div class="d-flex align-items-center">
<ng-icon [name]="overallHealth.icon" class="me-2"></ng-icon>
<small class="fw-medium">{{ overallHealth.message }}</small>
</div>
</div>
<button class="btn btn-sm btn-outline-danger"
(click)="checkSystemHealth()"
[disabled]="loading.healthCheck">
<ng-icon name="lucideRefreshCw" [class.spin]="loading.healthCheck"></ng-icon>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div *ngFor="let service of systemHealth" class="col-xl-3 col-md-6">
<div class="card h-100" [ngClass]="'border-' + service.color">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<h6 class="mb-0">{{ service.service }}</h6>
<small class="text-muted">{{ service.url }}</small>
</div>
<div class="d-flex flex-column align-items-end">
<span class="badge mb-1" [ngClass]="'bg-' + service.color">
{{ service.status }}
</span>
<small class="text-muted" [ngClass]="getStatusCodeClass(service.statusCode)">
{{ service.statusCode }}
</small>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mt-3">
<div>
<small class="text-muted">
<ng-icon name="lucideClock" class="me-1"></ng-icon>
{{ formatTimeAgo(service.checkedAt) }}
</small>
</div>
<div *ngIf="service.responseTime">
<small class="text-muted" [ngClass]="getResponseTimeClass(service.responseTime)">
{{ service.responseTime }}
</small>
</div>
</div>
<div *ngIf="service.error" class="mt-2">
<small class="text-danger">
<ng-icon name="lucideAlertTriangle" class="me-1"></ng-icon>
{{ service.error }}
</small>
</div>
</div>
</div>
</div>
</div>
<div class="mt-3 text-center">
<small class="text-muted">
<ng-icon name="lucideInfo" class="me-1"></ng-icon>
Vérification automatique toutes les 5 minutes
</small>
</div>
</div>
</div>
</div>
<!-- ==================== SECTION DES TABLEAUX ==================== -->
<div *ngIf="shouldShowTransactions()" class="tables-section">
<div class="row g-4">
<!-- Transactions récentes -->
<div class="col-xl-6">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon name="lucideListChecks" class="text-primary me-2"></ng-icon>
Transactions récentes
<span class="badge ms-2"
[ngClass]="isViewingGlobal() ? 'bg-info' : 'bg-success'">
{{ getCurrentMerchantName() }}
</span>
</h5>
<p class="text-muted small mb-0">Dernières 24 heures</p>
</div>
<div>
<button class="btn btn-sm btn-outline-primary"
(click)="refreshData()"
[disabled]="loading.globalData || loading.merchantData">
<ng-icon name="lucideRefreshCw"
[class.spin]="loading.globalData || loading.merchantData"></ng-icon>
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="ps-3">Période</th>
<th class="text-end">Montant</th>
<th class="text-end">Transactions</th>
<th class="text-end pe-3">Succès</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of (getCurrentTransactionData()?.items?.slice(0, 5) || [])">
<td class="ps-3">
<div>{{ item.period }}</div>
<small class="text-muted">{{ isViewingGlobal() ? 'Tous merchants' : 'Merchant ' + merchantId }}</small>
</td>
<td class="text-end">
<div class="fw-medium">{{ formatCurrency(item.totalAmount) }}</div>
</td>
<td class="text-end">
<div>{{ formatNumber(item.count) }}</div>
</td>
<td class="text-end pe-3">
<span [ngClass]="getSuccessRateClass((item.count > 0 ? (item.successCount / item.count) * 100 : 0))">
{{ (item.count > 0 ? (item.successCount / item.count) * 100 : 0) | number:'1.1-1' }}%
</span>
</td>
</tr>
<tr *ngIf="!getCurrentTransactionData()?.items?.length">
<td colspan="4" class="text-center text-muted py-5">
<ng-icon name="lucideDatabase" class="fs-1 opacity-25 mb-3 d-block"></ng-icon>
<p class="mb-0">Aucune transaction disponible</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer bg-transparent border-top-0">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<ng-icon name="lucideInfo" class="me-1"></ng-icon>
{{ getCurrentTransactionData()?.items?.length || 0 }} périodes au total
</small>
<a href="#" class="btn btn-sm btn-outline-primary">Voir tout</a>
</div>
</div>
</div>
</div>
<!-- Alertes système - Masqué si pas autorisé -->
<div *ngIf="shouldShowAlerts()" class="col-xl-6">
<div class="card h-100">
<div class="card-header bg-transparent border-bottom-0">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-0">
<ng-icon name="lucideBell" class="text-warning me-2"></ng-icon>
Alertes système
</h5>
<p class="text-muted small mb-0">Notifications en temps réel</p>
</div>
<div>
<span class="badge" [ngClass]="getAlertBadgeClass()">
{{ alerts.length }}
</span>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="alert-list">
<div *ngFor="let alert of alerts.slice(0, 5)" class="alert-item p-3 border-bottom">
<div class="d-flex align-items-start">
<div class="me-3">
<ng-icon *ngIf="alert.type === 'warning'"
name="lucideAlertTriangle"
class="text-warning fs-5"></ng-icon>
<ng-icon *ngIf="alert.type === 'info'"
name="lucideInfo"
class="text-info fs-5"></ng-icon>
<ng-icon *ngIf="alert.type === 'success'"
name="lucideCheckCircle2"
class="text-success fs-5"></ng-icon>
<ng-icon *ngIf="alert.type === 'danger'"
name="lucideAlertCircle"
class="text-danger fs-5"></ng-icon>
</div>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-start mb-1">
<h6 class="mb-0">{{ alert.title }}</h6>
<small class="text-muted">{{ formatTimeAgo(alert.timestamp) }}</small>
</div>
<p class="mb-0 text-muted small">{{ alert.description }}</p>
</div>
</div>
</div>
<div *ngIf="alerts.length === 0" class="text-center text-muted py-5">
<ng-icon name="lucideCheckCircle2" class="text-success fs-1 opacity-25 mb-3 d-block"></ng-icon>
<p class="mb-0">Aucune alerte active</p>
<small class="text-muted">Tous les systèmes fonctionnent normalement</small>
</div>
</div>
</div>
<div class="card-footer bg-transparent border-top-0">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<ng-icon name="lucideClock" class="me-1"></ng-icon>
Dernière vérification: {{ formatTimeAgo(alerts[0]?.timestamp) || 'Jamais' }}
</small>
<button class="btn btn-sm btn-outline-warning" (click)="checkSystemHealth()">
<ng-icon name="lucideRefreshCw" class="me-1"></ng-icon>
Vérifier
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="dashboard-footer mt-4">
<div class="card bg-light">
<div class="card-body py-2">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted small">
<ng-icon name="lucideCode" class="me-1"></ng-icon>
Dashboard FinTech v2.0 •
<ng-icon [name]="currentRoleIcon" class="me-1 ms-2"></ng-icon>
{{ currentRoleLabel }}
</div>
<div class="text-muted small">
<ng-icon [name]="isViewingGlobal() ? 'lucideGlobe' : 'lucideStore'" class="me-1"></ng-icon>
{{ getCurrentMerchantName() }}
</div>
<div class="text-muted small">
<ng-icon name="lucideClock" class="me-1"></ng-icon>
Mise à jour: {{ lastUpdated | date:'dd/MM/yyyy HH:mm:ss' }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -34,10 +34,11 @@ import {
HealthCheckStatus, HealthCheckStatus,
ChartDataNormalized, ChartDataNormalized,
ReportPeriod ReportPeriod
} from '../models/dcb-reporting.models'; } from './models/dcb-reporting.models';
import { ReportService } from '../services/dcb-reporting.service'; import { ReportService } from './services/dcb-reporting.service';
import { DashboardAccess, AllowedMerchant, DashboardAccessService } from '../services/dashboard-access.service'; import { DashboardAccess, AllowedMerchant, DashboardAccessService } from './services/dashboard-access.service';
import { AuthService } from '@core/services/auth.service'; import { AuthService } from '@core/services/auth.service';
import { PageTitle } from '@app/components/page-title/page-title';
// ============ TYPES ET INTERFACES ============ // ============ TYPES ET INTERFACES ============
@ -132,7 +133,12 @@ interface SubscriptionStats {
templateUrl: './dcb-reporting-dashboard.html', templateUrl: './dcb-reporting-dashboard.html',
styleUrls: ['./dcb-reporting-dashboard.css'], styleUrls: ['./dcb-reporting-dashboard.css'],
standalone: true, standalone: true,
imports: [CommonModule, FormsModule, NgIconComponent, NgbDropdownModule], imports: [
CommonModule,
FormsModule,
NgIconComponent,
NgbDropdownModule,
PageTitle],
providers: [ providers: [
provideIcons({ provideIcons({
lucideActivity, lucideAlertCircle, lucideCheckCircle2, lucideRefreshCw, lucideActivity, lucideAlertCircle, lucideCheckCircle2, lucideRefreshCw,
@ -197,7 +203,7 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
]; ];
// ============ PARAMÈTRES ============ // ============ PARAMÈTRES ============
merchantId: number | null = null; merchantId: number | undefined = undefined;
startDate: string = new Date().toISOString().split('T')[0]; startDate: string = new Date().toISOString().split('T')[0];
endDate: string = new Date().toISOString().split('T')[0]; endDate: string = new Date().toISOString().split('T')[0];
currentYear = new Date().getFullYear(); currentYear = new Date().getFullYear();
@ -315,42 +321,58 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
} }
} }
// ============ INITIALISATION ============
private initializeAccess(): void { private initializeAccess(): void {
this.access = this.accessService.getDashboardAccess(); this.access = this.accessService.getDashboardAccess();
this.currentRoleLabel = this.access.roleLabel; this.currentRoleLabel = this.access.roleLabel;
this.currentRoleIcon = this.access.roleIcon; this.currentRoleIcon = this.access.roleIcon;
const merchantPartnerId = this.access.merchantId; // Récupérer le merchant ID du service d'accès
const merchantPartnerId = this.getCurrentMerchantPartnerId();
if (this.access.isMerchantUser) { if (this.access.isMerchantUser) {
// Pour les merchant users, vérifier que l'ID est valide
if (merchantPartnerId) { if (merchantPartnerId) {
// Utiliser le merchantId du cache directement s'il existe this.merchantId = Number(merchantPartnerId);
if (this.access.merchantId) {
this.merchantId = this.access.merchantId;
} else {
// Sinon, essayer de le récupérer
const idNum = Number(merchantPartnerId);
this.merchantId = isNaN(idNum) ? null : idNum;
}
if (this.merchantId) {
this.accessService.setSelectedMerchantId(this.merchantId); this.accessService.setSelectedMerchantId(this.merchantId);
this.isViewingGlobalData = false; this.isViewingGlobalData = false;
}
console.log(`Merchant User: ID = ${this.merchantId}`);
} else {
console.error('Merchant ID invalide pour Merchant User:', merchantPartnerId);
this.addAlert('danger', 'Erreur de configuration',
'Impossible de déterminer le merchant ID', 'Maintenant');
this.isViewingGlobalData = false;
} }
} else if (this.access.isHubUser) { } else if (this.access.isHubUser) {
// Pour les hub users, vérifier si un merchant est sélectionné
const selectedMerchantId = this.accessService.getSelectedMerchantId(); const selectedMerchantId = this.accessService.getSelectedMerchantId();
if (selectedMerchantId) {
if (selectedMerchantId && selectedMerchantId > 0) {
this.merchantId = selectedMerchantId; this.merchantId = selectedMerchantId;
this.isViewingGlobalData = false; this.isViewingGlobalData = false;
console.log(`Hub User: Merchant sélectionné = ${this.merchantId}`);
} else { } else {
this.isViewingGlobalData = true; this.isViewingGlobalData = true;
} this.merchantId = undefined;
console.log('Hub User: Mode global (aucun merchant sélectionné)');
} }
} }
// ============ INITIALISATION ============ // Mettre à jour la sélection de données
this.dataSelection.merchantPartnerId = this.isViewingGlobalData ?
undefined : this.merchantId;
}
isValidMerchantId(id: any): boolean {
if (id === null || id === undefined) {
return false;
}
const numId = Number(id);
return !isNaN(numId) && Number.isInteger(numId) && numId > 0;
}
private loadAllowedMerchants(): void { private loadAllowedMerchants(): void {
this.subscriptions.push( this.subscriptions.push(
@ -368,7 +390,6 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
} }
private initializeDashboard(): void { private initializeDashboard(): void {
console.log(`Dashboard initialisé pour: ${this.currentRoleLabel} (${this.access.isHubUser ? 'Hub User' : 'Merchant User'})`);
if (this.access.isHubUser) { if (this.access.isHubUser) {
if (this.isViewingGlobalData) { if (this.isViewingGlobalData) {
@ -420,14 +441,29 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
); );
} }
private loadMerchantData(merchantId: number | null): void { private loadMerchantData(merchantId: number | undefined): void {
console.log('Chargement des données pour merchant:', merchantId); console.log('Chargement des données pour merchant:', merchantId);
if (!merchantId) { // Vérification plus robuste
console.error('Merchant ID invalide:', merchantId); if (!merchantId || merchantId <= 0 || isNaN(merchantId)) {
this.addAlert('danger', 'Erreur', 'Merchant ID invalide', 'Maintenant'); console.error('Merchant ID invalide ou manquant:', merchantId);
this.addAlert('warning', 'Merchant non spécifié',
'Veuillez sélectionner un merchant valide', 'Maintenant');
// Pour les merchant users, essayer de récupérer l'ID depuis le cache
if (this.access.isMerchantUser) {
const cachedId = this.accessService.getSelectedMerchantId();
if (cachedId && cachedId > 0) {
merchantId = cachedId;
this.merchantId = cachedId;
console.log(`Utilisation du merchant ID du cache: ${merchantId}`);
} else {
return; return;
} }
} else {
return;
}
}
this.loading.merchantData = true; this.loading.merchantData = true;
this.lastUpdated = new Date(); this.lastUpdated = new Date();
@ -452,7 +488,8 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
error: (err) => { error: (err) => {
console.error(`Erreur lors du chargement des données du merchant ${merchantId}:`, err); console.error(`Erreur lors du chargement des données du merchant ${merchantId}:`, err);
this.loading.merchantData = false; this.loading.merchantData = false;
this.addAlert('danger', 'Erreur de chargement', `Impossible de charger les données du merchant ${merchantId}`, 'Maintenant'); this.addAlert('danger', 'Erreur de chargement',
`Impossible de charger les données du merchant ${merchantId}`, 'Maintenant');
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
}) })

View File

@ -75,11 +75,21 @@ export class DashboardAccessService {
const merchantPartnerId = authService.getCurrentMerchantPartnerId(); const merchantPartnerId = authService.getCurrentMerchantPartnerId();
if (!merchantPartnerId) return undefined; // Vérifier si la valeur existe et est numérique
if (!merchantPartnerId) {
console.warn('Aucun merchant ID trouvé pour l\'utilisateur');
return undefined;
}
const merchantId = parseInt(merchantPartnerId, 10); // Convertir en nombre en gérant les erreurs
const merchantId = Number(merchantPartnerId);
return isNaN(merchantId) ? undefined : merchantId; if (isNaN(merchantId) || !Number.isInteger(merchantId)) {
console.error(`Merchant ID invalide: ${merchantPartnerId}`);
return undefined;
}
return merchantId;
} }
/** /**

View File

@ -6,7 +6,7 @@ import { HubUsersManagement } from '@modules/hub-users-management/hub-users';
import { MerchantUsersManagement } from '@modules/hub-users-management/merchant-users'; import { MerchantUsersManagement } from '@modules/hub-users-management/merchant-users';
// Composants principaux // Composants principaux
import { DcbDashboard } from '@modules/dcb-dashboard/dcb-dashboard'; import { DcbReportingDashboard } from '@modules/dcb-dashboard/dcb-reporting-dashboard';
import { Team } from '@modules/team/team'; import { Team } from '@modules/team/team';
import { Transactions } from '@modules/transactions/transactions'; import { Transactions } from '@modules/transactions/transactions';
import { OperatorsConfig } from '@modules/operators/config/config'; import { OperatorsConfig } from '@modules/operators/config/config';
@ -32,7 +32,7 @@ const routes: Routes = [
{ {
path: 'dcb-dashboard', path: 'dcb-dashboard',
canActivate: [authGuard, roleGuard], canActivate: [authGuard, roleGuard],
component: DcbDashboard, component: DcbReportingDashboard,
data: { data: {
title: 'Dashboard DCB', title: 'Dashboard DCB',
module: 'dcb-dashboard' module: 'dcb-dashboard'