diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index c3bd036..b8e36ea 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -3,9 +3,6 @@ import { VerticalLayout } from '@layouts/vertical-layout/vertical-layout'; import { authGuard } from './core/guards/auth.guard'; export const routes: Routes = [ - // Redirection racine - { path: '', redirectTo: '/dcb-dashboard', pathMatch: 'full' }, - // ===== ROUTES D'ERREUR (publiques) ===== { path: 'error', diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index 0385a30..1c6c422 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -66,7 +66,6 @@ export class AuthService { private userProfile$ = new BehaviorSubject(null); private initialized$ = new BehaviorSubject(false); - private readonly dashboardAccessService = inject(DashboardAccessService); private readonly transactionAccessService = inject(TransactionAccessService); // === INITIALISATION DE L'APPLICATION === @@ -198,14 +197,14 @@ export class AuthService { ).pipe( tap(() => { this.clearAuthData(); - this.dashboardAccessService.clearCache(); + this.transactionAccessService.clearCache(); this.clearAllStorage(); // Nettoyer tout le storage }), catchError(error => { // Même en cas d'erreur, nettoyer tout this.clearAuthData(); - this.dashboardAccessService.clearCache(); + this.transactionAccessService.clearCache(); this.clearAllStorage(); console.warn('Logout API error, but local data cleared:', error); @@ -215,7 +214,7 @@ export class AuthService { finalize(() => { // Garantir le nettoyage dans tous les cas this.clearAuthData(); - this.dashboardAccessService.clearCache(); + this.transactionAccessService.clearCache(); }) ); @@ -226,7 +225,7 @@ export class AuthService { */ forceLogout(): void { this.clearAuthData(); - this.dashboardAccessService.clearCache(); + this.transactionAccessService.clearCache(); this.clearAllStorage(); } @@ -236,7 +235,7 @@ export class AuthService { */ private clearAuthData(): void { - this.dashboardAccessService.clearCache(); + this.transactionAccessService.clearCache(); // Supprimer tous les tokens et données utilisateur @@ -375,6 +374,10 @@ export class AuthService { return this.userProfile$.asObservable(); } + getCurrentUserProfile(): User | null { + return this.userProfile$.value; + } + // === GESTION DES RÔLES ET TYPES === getCurrentUserRoles(): UserRole[] { @@ -545,10 +548,42 @@ export class AuthService { /** * Récupère le merchantPartnerId de l'utilisateur courant (si marchand) + * Retourne string pour compatibilité, mais devrait être converti en number ailleurs */ getCurrentMerchantPartnerId(): string | null { const profile = this.userProfile$.value; - return profile?.merchantPartnerId || null; + + if (!profile) { + return null; + } + + const merchantId = profile.merchantPartnerId + + if (merchantId === null || merchantId === undefined) { + return null; + } + + return merchantId.toString(); + } + + /** + * Version qui retourne un number (pour usage interne) + */ + getCurrentMerchantPartnerIdAsNumber(): number | null { + const merchantIdStr = this.getCurrentMerchantPartnerId(); + + if (!merchantIdStr) { + return null; + } + + const merchantId = Number(merchantIdStr); + + if (isNaN(merchantId) || merchantId <= 0 || !Number.isInteger(merchantId)) { + console.error(`Merchant ID invalide: ${merchantIdStr}`); + return null; + } + + return merchantId; } /** diff --git a/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts b/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts index 0c93e5a..f2d5fc3 100644 --- a/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts +++ b/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts @@ -309,62 +309,96 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit { Chart.register(...registerables); } - ngOnInit(): void { - this.initializeAccess(); - this.loadAllowedMerchants(); + ngOnInit(): void { + // 1. Initialiser l'accès + this.initializeAccess(); + + // 2. Initialiser le dashboard (avec délai pour laisser le temps à l'accès) + setTimeout(() => { this.initializeDashboard(); - - if (this.accessService.shouldShowSystemHealth()) { - setInterval(() => { - this.checkSystemHealth(); - }, 5 * 60 * 1000); - } + }, 100); + + // 3. Charger les merchants (avec délai) + setTimeout(() => { + this.loadAllowedMerchants(); + }, 150); + + if (this.accessService.shouldShowSystemHealth()) { + setInterval(() => { + this.checkSystemHealth(); + }, 5 * 60 * 1000); } +} + // ============ INITIALISATION ============ private initializeAccess(): void { - this.access = this.accessService.getDashboardAccess(); - this.currentRoleLabel = this.access.roleLabel; - this.currentRoleIcon = this.access.roleIcon; + // Attendre que l'accès soit prêt + this.subscriptions.push( + this.accessService.waitForAccess().subscribe(() => { + this.access = this.accessService.getDashboardAccess(); + this.currentRoleLabel = this.access.roleLabel; + this.currentRoleIcon = this.access.roleIcon; - // Récupérer le merchant ID du service d'accès - const merchantPartnerId = this.getCurrentMerchantPartnerId(); + console.log('✅ Dashboard initialisé avec:', { + access: this.access, + merchantId: this.access.merchantId, + isHubUser: this.access.isHubUser, + }); - if (this.access.isMerchantUser) { - // Pour les merchant users, vérifier que l'ID est valide - if (merchantPartnerId) { - this.merchantId = Number(merchantPartnerId); - this.accessService.setSelectedMerchantId(this.merchantId); - 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) { - // Pour les hub users, vérifier si un merchant est sélectionné - const selectedMerchantId = this.accessService.getSelectedMerchantId(); - - if (selectedMerchantId && selectedMerchantId > 0) { - this.merchantId = selectedMerchantId; - this.isViewingGlobalData = false; - console.log(`Hub User: Merchant sélectionné = ${this.merchantId}`); - } else { - this.isViewingGlobalData = true; - this.merchantId = undefined; - console.log('Hub User: Mode global (aucun merchant sélectionné)'); - } - } - - // Mettre à jour la sélection de données - this.dataSelection.merchantPartnerId = this.isViewingGlobalData ? - undefined : this.merchantId; + // Pour les merchant users + if (this.access.isMerchantUser) { + const merchantId = this.access.merchantId; + + if (merchantId && merchantId > 0) { + this.merchantId = merchantId; + this.accessService.setSelectedMerchantId(merchantId); + this.isViewingGlobalData = false; + this.dataSelection.merchantPartnerId = merchantId; + + console.log(`✅ Merchant User: ID = ${this.merchantId}`); + } else { + console.error('❌ Merchant ID invalide pour Merchant User:', merchantId); + // Essayer de récupérer directement depuis le profil + const merchantPartnerId = this.authService.getCurrentMerchantPartnerId(); + if (merchantPartnerId) { + const id = Number(merchantPartnerId); + if (!isNaN(id) && id > 0) { + this.merchantId = id; + this.accessService.setSelectedMerchantId(id); + this.isViewingGlobalData = false; + this.dataSelection.merchantPartnerId = id; + console.log(`✅ Merchant ID récupéré depuis profil: ${id}`); + } + } else { + this.addAlert('danger', 'Erreur de configuration', + 'Impossible de déterminer le merchant ID', 'Maintenant'); + this.isViewingGlobalData = false; + } + } + } + // Pour les hub users + else if (this.access.isHubUser) { + const selectedMerchantId = this.accessService.getSelectedMerchantId(); + + if (selectedMerchantId && selectedMerchantId > 0) { + this.merchantId = selectedMerchantId; + this.isViewingGlobalData = false; + this.dataSelection.merchantPartnerId = selectedMerchantId; + console.log(`✅ Hub User: Merchant sélectionné = ${this.merchantId}`); + } else { + this.isViewingGlobalData = true; + this.merchantId = undefined; + this.dataSelection.merchantPartnerId = undefined; + console.log('✅ Hub User: Mode global (aucun merchant sélectionné)'); + } + } + }) + ); } + isValidMerchantId(id: any): boolean { if (id === null || id === undefined) { return false; @@ -390,7 +424,14 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit { } private initializeDashboard(): void { + // Vérifier d'abord si le profil est chargé + const profile = this.authService.getProfile(); + if (!profile) { + console.log('⏳ Profil non chargé, attente...'); + return; + } + if (this.access.isHubUser) { if (this.isViewingGlobalData) { this.loadGlobalData(); diff --git a/src/app/modules/dcb-dashboard/services/dashboard-access.service.ts b/src/app/modules/dcb-dashboard/services/dashboard-access.service.ts index 8ef0165..192d5d5 100644 --- a/src/app/modules/dcb-dashboard/services/dashboard-access.service.ts +++ b/src/app/modules/dcb-dashboard/services/dashboard-access.service.ts @@ -1,22 +1,16 @@ -import { inject, Injectable, Injector } from '@angular/core'; -import { Observable, of } from 'rxjs'; -import { map, catchError } from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { Observable, of, BehaviorSubject } from 'rxjs'; +import { map, catchError, switchMap, take, filter, first } from 'rxjs/operators'; import { UserRole, RoleManagementService } from '@core/services/hub-users-roles-management.service'; import { MerchantConfigService } from '@modules/merchant-config/merchant-config.service'; import { AuthService } from '@core/services/auth.service'; -// Interface minimaliste export interface DashboardAccess { - // Type d'utilisateur - CORE isHubUser: boolean; isMerchantUser: boolean; - - // Info du rôle roleLabel: string; roleIcon: string; userRole: UserRole; - - // Merchant info (seulement pour merchant users) merchantId?: number; } @@ -30,16 +24,48 @@ export class DashboardAccessService { private accessCache: DashboardAccess | null = null; private merchantsCache: AllowedMerchant[] | null = null; private currentMerchantId: number | null = null; - - private readonly injector = inject(Injector); + private accessReady$ = new BehaviorSubject(false); constructor( private roleService: RoleManagementService, - private merchantService: MerchantConfigService - ) {} + private merchantService: MerchantConfigService, + private authService: AuthService + ) { + // S'abonner aux changements du profil utilisateur + this.initializeProfileSubscription(); + } /** - * Obtient l'accès simplifié + * Initialise la surveillance du profil utilisateur + */ + private initializeProfileSubscription(): void { + this.authService.getUserProfile().pipe( + filter(profile => profile !== null), + first() + ).subscribe(profile => { + console.log('📊 DashboardAccessService: Profil utilisateur chargé', { + username: profile.username, + merchantPartnerId: profile.merchantPartnerId, + userType: profile.userType + }); + + this.clearCache(); + this.accessReady$.next(true); + }); + } + + /** + * Attend que l'accès soit prêt + */ + waitForAccess(): Observable { + return this.accessReady$.pipe( + filter(ready => ready), + take(1) + ); + } + + /** + * Obtient l'accès dashboard (version synchrone) */ getDashboardAccess(): DashboardAccess { if (this.accessCache) { @@ -49,92 +75,174 @@ export class DashboardAccessService { const userRole = this.roleService.getCurrentRole(); const isHubUser = this.roleService.isHubUser(); + let merchantId: number | undefined = undefined; + + if (!isHubUser) { + merchantId = this.getMerchantIdForCurrentUser(); + } + const access: DashboardAccess = { isHubUser, isMerchantUser: !isHubUser, roleLabel: this.roleService.getRoleLabel(), roleIcon: this.roleService.getRoleIcon(), userRole: userRole || UserRole.DCB_SUPPORT, + merchantId }; - // Pour les merchant users, définir leur merchant ID - if (!isHubUser) { - access.merchantId = this.getMerchantIdForUser(); - } - + console.log('📊 DashboardAccess créé:', { + ...access, + merchantId, + userRoleLabel: userRole + }); + this.accessCache = access; return access; } /** - * Obtient le merchant ID pour un merchant user + * Obtient l'accès dashboard (version asynchrone) */ - private getMerchantIdForUser(): number | undefined { - // Récupérer le merchant ID de l'utilisateur courant - const authService = this.injector.get(AuthService); - - const merchantPartnerId = authService.getCurrentMerchantPartnerId(); - - // Vérifier si la valeur existe et est numérique - if (!merchantPartnerId) { - console.warn('Aucun merchant ID trouvé pour l\'utilisateur'); - return undefined; - } - - // Convertir en nombre en gérant les erreurs - const merchantId = Number(merchantPartnerId); - - if (isNaN(merchantId) || !Number.isInteger(merchantId)) { - console.error(`Merchant ID invalide: ${merchantPartnerId}`); - return undefined; - } - - return merchantId; + getDashboardAccessAsync(): Observable { + return this.waitForAccess().pipe( + map(() => this.getDashboardAccess()) + ); } /** - * Obtient les merchants disponibles - * - Hub users: tous les merchants - * - Merchant users: seulement leur merchant + * Récupère le merchant ID pour l'utilisateur courant + */ + private getMerchantIdForCurrentUser(): number | undefined { + // Utiliser la méthode optimisée d'AuthService + const merchantId = this.authService.getCurrentMerchantPartnerIdAsNumber(); + + if (this.isValidMerchantId(merchantId)) { + console.log(`✅ Merchant ID récupéré: ${merchantId}`); + return merchantId; + } + + console.warn('⚠️ Aucun merchant ID valide trouvé pour l\'utilisateur'); + console.log('Debug:', { + merchantId, + profile: this.authService.getCurrentUserProfile(), + isAuthenticated: this.authService.isAuthenticated() + }); + + return undefined; + } + + /** + * Valide si un ID marchand est valide + */ + private isValidMerchantId(id: any): id is number { + if (id === null || id === undefined) return false; + + const numId = Number(id); + return !isNaN(numId) && + Number.isInteger(numId) && + numId > 0; + } + + /** + * Obtient la liste des merchants disponibles */ getAvailableMerchants(): Observable { - if (this.merchantsCache) { - return of(this.merchantsCache); - } + return this.waitForAccess().pipe( + switchMap(() => { + if (this.merchantsCache) { + return of(this.merchantsCache); + } - const access = this.getDashboardAccess(); - - if (access.isHubUser) { - // Hub users voient tous les merchants - return this.merchantService.getAllMerchants().pipe( - map(merchants => { - const availableMerchants: any[] = merchants.map(m => ({ - id: m.id, - name: m.name - })); - this.merchantsCache = availableMerchants; - return availableMerchants; - }), - catchError(error => { - console.error('Erreur chargement merchants:', error); - return of([]); - }) - ); - } else { - // Merchant users: seulement leur merchant - const merchantId = access.merchantId || this.getMerchantIdForUser(); - return of([{ - id: merchantId, - name: `Merchant ${merchantId}` - }]); - } + const access = this.getDashboardAccess(); + + console.log('📊 getAvailableMerchants pour:', { + isHubUser: access.isHubUser, + merchantId: access.merchantId, + role: access.userRole + }); + + if (access.isHubUser) { + // Hub users: tous les merchants + option globale + return this.loadAllMerchantsForHubUser(); + } else { + // Merchant users: seulement leur merchant + return this.loadSingleMerchantForUser(access.merchantId); + } + }) + ); } /** - * Définit le merchant sélectionné + * Charge tous les merchants pour les hub users + */ + private loadAllMerchantsForHubUser(): Observable { + return this.merchantService.getAllMerchants().pipe( + map(merchants => { + const availableMerchants: AllowedMerchant[] = merchants.map(m => ({ + id: m.id, + name: m.name + })); + + // Ajouter l'option "Global" + availableMerchants.unshift({ + id: 0, + name: '🌐 Données globales' + }); + + this.merchantsCache = availableMerchants; + return availableMerchants; + }), + catchError(error => { + console.error('❌ Erreur lors du chargement des merchants:', error); + // Retourner au moins l'option globale + return of([{ + id: 0, + name: '🌐 Données globales' + }]); + }) + ); + } + + /** + * Charge le merchant unique pour un merchant user + */ + private loadSingleMerchantForUser(merchantId?: number): Observable { + if (!merchantId || merchantId <= 0) { + console.warn('⚠️ Aucun merchant ID valide pour merchant user'); + + // Dernière tentative de récupération + const fallbackId = this.getMerchantIdForCurrentUser(); + if (this.isValidMerchantId(fallbackId)) { + const merchants = [{ + id: fallbackId, + name: `🏪 Merchant ${fallbackId}` + }]; + this.merchantsCache = merchants; + return of(merchants); + } + + return of([]); + } + + const merchants: AllowedMerchant[] = [{ + id: merchantId, + name: `🏪 Merchant ${merchantId}` + }]; + + this.merchantsCache = merchants; + return of(merchants); + } + + /** + * Définit le merchant sélectionné (pour hub users) */ setSelectedMerchantId(merchantId: number): void { - this.currentMerchantId = merchantId; + if (this.getDashboardAccess().isHubUser) { + this.currentMerchantId = merchantId; + console.log(`📌 Merchant sélectionné: ${merchantId}`); + } else { + console.warn('⚠️ Seuls les hub users peuvent sélectionner un merchant'); + } } /** @@ -148,7 +256,7 @@ export class DashboardAccessService { return access.merchantId || null; } - // Hub users: le merchant sélectionné ou le premier + // Hub users: le merchant sélectionné return this.currentMerchantId; } @@ -158,9 +266,8 @@ export class DashboardAccessService { canAccessMerchant(merchantId: number): Observable { const access = this.getDashboardAccess(); - // Hub users: tous les merchants sont accessibles if (access.isHubUser) { - return of(true); + return of(true); // Hub users: accès à tous } // Merchant users: seulement leur merchant @@ -174,13 +281,30 @@ export class DashboardAccessService { this.accessCache = null; this.merchantsCache = null; this.currentMerchantId = null; + console.log('🗑️ DashboardAccessService: Cache nettoyé'); } /** - * Méthodes utilitaires pour le template + * Méthode de débogage */ - - // Pour les Hub Users + debug(): void { + console.log('=== 🔍 DEBUG DashboardAccessService ==='); + console.log('1. Access cache:', this.accessCache); + console.log('2. Merchants cache:', this.merchantsCache); + console.log('3. Current merchant ID:', this.currentMerchantId); + console.log('4. Access ready:', this.accessReady$.value); + + console.log('5. AuthService info:'); + console.log(' - Merchant ID (number):', this.authService.getCurrentMerchantPartnerIdAsNumber()); + console.log(' - Merchant ID (string):', this.authService.getCurrentMerchantPartnerId()); + console.log(' - Profil courant:', this.authService.getCurrentUserProfile()); + console.log(' - Token présent:', !!this.authService.getAccessToken()); + console.log(' - Is authenticated:', this.authService.isAuthenticated()); + console.log('=============================='); + } + + // ============ MÉTHODES UTILITAIRES POUR LE TEMPLATE ============ + shouldShowSystemHealth(): boolean { return this.getDashboardAccess().isHubUser; } @@ -199,40 +323,36 @@ export class DashboardAccessService { return access.isHubUser && access.userRole === UserRole.DCB_ADMIN; } - // Pour tous les utilisateurs shouldShowTransactions(): boolean { - return true; // Tous peuvent voir les transactions (mais scope différent) + return true; } shouldShowCharts(): boolean { - return true; // Tous peuvent voir les transactions (mais scope différent) + return true; } shouldShowKPIs(): boolean { - return true; // Tous peuvent voir les transactions (mais scope différent) + return true; } shouldShowAlerts(): boolean { - return true; // Tous peuvent voir les transactions (mais scope différent) + return true; } canRefreshData(): boolean { - return true; // Tous peuvent rafraîchir + return true; } - // Pour la sélection de merchant canSelectMerchant(): boolean { return this.getDashboardAccess().isHubUser; } - // Pour l'affichage du merchant ID shouldShowMerchantId(): boolean { const access = this.getDashboardAccess(); return access.isMerchantUser || (access.isHubUser && this.getSelectedMerchantId() !== null); } - // Pour l'édition du merchant filter canEditMerchantFilter(): boolean { const access = this.getDashboardAccess(); if (access.isHubUser) { @@ -240,4 +360,55 @@ export class DashboardAccessService { } return access.userRole === UserRole.DCB_PARTNER_ADMIN; } + + // ============ MÉTHODES UTILITAIRES SUPPLÉMENTAIRES ============ + + /** + * Vérifie si l'utilisateur peut voir les données globales + */ + canViewGlobalData(): boolean { + return this.getDashboardAccess().isHubUser; + } + + /** + * Vérifie si l'utilisateur est en mode "données globales" + */ + isViewingGlobalData(): boolean { + const access = this.getDashboardAccess(); + if (access.isHubUser) { + return this.currentMerchantId === 0 || this.currentMerchantId === null; + } + return false; + } + + /** + * Retourne le nom du merchant courant + */ + getCurrentMerchantName(): string { + const access = this.getDashboardAccess(); + + if (access.isMerchantUser && access.merchantId) { + return `Merchant ${access.merchantId}`; + } + + if (access.isHubUser) { + if (this.currentMerchantId === 0 || this.currentMerchantId === null) { + return 'Données globales'; + } else if (this.currentMerchantId) { + return `Merchant ${this.currentMerchantId}`; + } + } + + return 'Inconnu'; + } + + /** + * Réinitialise complètement le service + */ + reset(): void { + this.clearCache(); + this.accessReady$.next(false); + // Réinitialiser la surveillance du profil + this.initializeProfileSubscription(); + } } \ No newline at end of file