diff --git a/src/app/core/models/dcb-bo-hub-subscription.model.ts b/src/app/core/models/dcb-bo-hub-subscription.model.ts index a64ac9d..a619d42 100644 --- a/src/app/core/models/dcb-bo-hub-subscription.model.ts +++ b/src/app/core/models/dcb-bo-hub-subscription.model.ts @@ -22,7 +22,7 @@ export enum Currency { // === MODÈLE SUBSCRIPTION PRINCIPAL === export interface Subscription { - id: number; + id: string; externalReference?: string | null; periodicity: SubscriptionPeriodicity; startDate: string; diff --git a/src/app/core/models/dcb-bo-hub-user.model.ts b/src/app/core/models/dcb-bo-hub-user.model.ts index 6d54d92..6c44a61 100644 --- a/src/app/core/models/dcb-bo-hub-user.model.ts +++ b/src/app/core/models/dcb-bo-hub-user.model.ts @@ -152,14 +152,27 @@ export interface RoleOperationResponse { // === SEARCH === export interface SearchUsersParams { query?: string; - role?: UserRole; + role?: string; enabled?: boolean; userType?: UserType; merchantPartnerId?: string; + searchTerm?: string; + status?: 'all' | 'enabled' | 'disabled'; + emailVerified?: 'all' | 'verified' | 'not-verified'; + sortField?: keyof User; + sortDirection?: 'asc' | 'desc'; page?: number; limit?: number; } +export interface UserStats { + total: number; + enabled: number; + disabled: number; + emailVerified: number; + roleCounts: Record; +} + // === UTILITAIRES === export class UserUtils { static isHubUser(user: User): boolean { diff --git a/src/app/core/models/merchant-config.model.ts b/src/app/core/models/merchant-config.model.ts index a219560..18bd09c 100644 --- a/src/app/core/models/merchant-config.model.ts +++ b/src/app/core/models/merchant-config.model.ts @@ -111,6 +111,7 @@ export interface ApiMerchantUser { email?: string; firstName?: string; lastName?: string; + merchantPartnerId?: number; createdAt?: string; updatedAt?: string; } @@ -129,6 +130,10 @@ export interface ApiMerchant { updatedAt?: string; } +export interface MerchantUserWithMerchant extends MerchantUser { + merchant: Merchant; +} + // === DTOs CRUD === export interface CreateMerchantDto { name: string; diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index 1c6c422..e8b6041 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -1,18 +1,14 @@ -import { Injectable, inject } from '@angular/core'; +import { Injectable, inject, EventEmitter } from '@angular/core'; import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; -import { Router } from '@angular/router'; import { environment } from '@environments/environment'; -import { BehaviorSubject, Observable, throwError, tap, catchError, finalize, of } from 'rxjs'; +import { BehaviorSubject, Observable, throwError, tap, catchError, finalize, of, filter, take } from 'rxjs'; import { firstValueFrom } from 'rxjs'; -import { DashboardAccessService } from '@modules/dcb-dashboard/services/dashboard-access.service'; - import { User, UserType, UserRole, } from '@core/models/dcb-bo-hub-user.model'; -import { TransactionAccessService } from '@modules/transactions/services/transaction-access.service'; // === INTERFACES DTO AUTH === export interface LoginDto { @@ -66,18 +62,32 @@ export class AuthService { private userProfile$ = new BehaviorSubject(null); private initialized$ = new BehaviorSubject(false); - private readonly transactionAccessService = inject(TransactionAccessService); + // Observable pour les changements d'état d'authentification + private authStateChanged$ = new BehaviorSubject(this.isAuthenticated()); + + private profileLoaded$ = new BehaviorSubject(false); + + // Événement émis lors de la déconnexion + private logoutEvent = new EventEmitter(); + + // Observable pour les autres services + onLogout$ = this.logoutEvent.asObservable(); // === INITIALISATION DE L'APPLICATION === /** * Initialise l'authentification au démarrage de l'application */ + + /** + * Initialise l'authentification et charge le profil + */ async initialize(): Promise { const token = this.getAccessToken(); // Pas de token → pas authentifié if (!token) { + this.profileLoaded$.next(true); this.initialized$.next(true); return false; } @@ -85,6 +95,7 @@ export class AuthService { // Token expiré → tenter refresh if (this.isTokenExpired(token)) { const ok = await this.tryRefreshToken(); + this.profileLoaded$.next(true); this.initialized$.next(true); return ok; } @@ -93,14 +104,49 @@ export class AuthService { try { await firstValueFrom(this.loadUserProfile()); this.authState$.next(true); + this.profileLoaded$.next(true); this.initialized$.next(true); return true; } catch { this.clearAuthData(); + this.profileLoaded$.next(true); this.initialized$.next(true); return false; } } + + /** + * Attendre que le profil soit chargé + */ + waitForProfile(): Observable { + return this.profileLoaded$.pipe( + filter(loaded => loaded), + take(1) + ); + } + + /** + * Vérifier si le profil est chargé + */ + isProfileLoaded(): boolean { + return this.profileLoaded$.value; + } + + /** + * Forcer le chargement du profil + */ + loadProfileIfNeeded(): Observable { + if (this.userProfile$.value) { + return of(this.userProfile$.value); + } + + return this.loadUserProfile(); + } + + // Émettre les changements d'état + getAuthStateChanged(): Observable { + return this.authStateChanged$.asObservable(); + } /** * Tente de rafraîchir le token de manière synchrone @@ -171,21 +217,16 @@ export class AuthService { } /** - * Déconnexion utilisateur - */ -/** * Déconnexion utilisateur avec nettoyage complet */ logout(): Observable { const token = this.getAccessToken(); - // Si pas de token, nettoyer et retourner un observable complet if (!token) { - this.clearAuthData(); + this.performLogoutCleanup(); // Nettoyage local return of({ message: 'Already logged out' }); } - // Ajouter le token dans le header si nécessaire const headers = new HttpHeaders({ 'Authorization': `Bearer ${token}` }); @@ -196,47 +237,43 @@ export class AuthService { { headers } ).pipe( tap(() => { - this.clearAuthData(); - - this.transactionAccessService.clearCache(); - this.clearAllStorage(); // Nettoyer tout le storage + this.performLogoutCleanup(); }), catchError(error => { - // Même en cas d'erreur, nettoyer tout - this.clearAuthData(); - - this.transactionAccessService.clearCache(); - this.clearAllStorage(); + this.performLogoutCleanup(); console.warn('Logout API error, but local data cleared:', error); - // Retourner un succès simulé pour permettre la navigation return of({ message: 'Local session cleared' }); }), finalize(() => { - // Garantir le nettoyage dans tous les cas - this.clearAuthData(); - - this.transactionAccessService.clearCache(); + this.performLogoutCleanup(); }) ); } - - /** - * Déconnexion forcée sans appel API - */ - forceLogout(): void { - this.clearAuthData(); + + private performLogoutCleanup(): void { + // 1. Nettoyer les données d'authentification + localStorage.removeItem(this.tokenKey); + localStorage.removeItem(this.refreshTokenKey); + localStorage.removeItem('user_profile'); + sessionStorage.clear(); - this.transactionAccessService.clearCache(); - this.clearAllStorage(); + // 2. Réinitialiser les BehaviorSubjects + this.authState$.next(false); + this.userProfile$.next(null); + + // 3. Émettre l'événement de déconnexion + this.logoutEvent.emit(); } + + forceLogout(): void { + this.performLogoutCleanup(); + } + /** * Nettoyer toutes les données d'authentification */ private clearAuthData(): void { - - - this.transactionAccessService.clearCache(); // Supprimer tous les tokens et données utilisateur localStorage.removeItem(this.tokenKey); @@ -250,6 +287,7 @@ export class AuthService { // Réinitialiser les BehaviorSubjects this.authState$.next(false); this.userProfile$.next(null); + this.authStateChanged$.next(false); } /** @@ -306,19 +344,29 @@ export class AuthService { * Détermine le type d'utilisateur basé sur la réponse API */ private determineUserType(apiUser: any): UserType { + + const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT]; + const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT]; - const hubRoles = [UserRole.DCB_ADMIN || UserRole.DCB_SUPPORT]; - const merchantRoles = [UserRole.DCB_PARTNER_ADMIN || UserRole.DCB_PARTNER_MANAGER || UserRole.DCB_PARTNER_SUPPORT]; - - // Logique pour déterminer le type d'utilisateur - if (apiUser.clientRoles?.[0].includes(merchantRoles)) { - return UserType.MERCHANT_PARTNER; - } else if (apiUser.clientRoles?.[0].includes(hubRoles)) { - return UserType.HUB; - } else { - console.warn('Type d\'utilisateur non reconnu, rôle:', apiUser.clientRoles?.[0]); - return UserType.HUB; // Fallback + // Récupérer le rôle depuis l'API + const clientRole = apiUser.clientRoles?.[0]; + + if (!clientRole) { + console.warn('Aucun rôle trouvé dans le profil'); + throw new Error(`Type d'utilisateur non reconnu: ${clientRole}`); } + + // Vérifier si c'est un rôle marchand + if (merchantRoles.some(role => clientRole.includes(role))) { + return UserType.MERCHANT_PARTNER; + } + + // Vérifier si c'est un rôle hub + if (hubRoles.some(role => clientRole.includes(role))) { + return UserType.HUB; + } + + throw new Error(`Type d'utilisateur non reconnu: ${clientRole}`); } private mapToUserModel(apiUser: any, userType: UserType): User { @@ -374,6 +422,9 @@ export class AuthService { return this.userProfile$.asObservable(); } + /** + * Récupérer le profil utilisateur (synchrone, après chargement) + */ getCurrentUserProfile(): User | null { return this.userProfile$.value; } @@ -552,11 +603,13 @@ export class AuthService { */ getCurrentMerchantPartnerId(): string | null { const profile = this.userProfile$.value; - + if (!profile) { return null; } + console.log("Merchant Partner ID : " + profile.merchantPartnerId) + const merchantId = profile.merchantPartnerId if (merchantId === null || merchantId === undefined) { diff --git a/src/app/core/services/hub-users-roles-management.service.ts b/src/app/core/services/hub-users-roles-management.service.ts index 84a45b4..f2de183 100644 --- a/src/app/core/services/hub-users-roles-management.service.ts +++ b/src/app/core/services/hub-users-roles-management.service.ts @@ -20,6 +20,88 @@ export enum UserRole { type RoleCategory = 'hub' | 'partner' | 'config'; +interface RoleConfig { + label: string; + description: string; + badgeClass: string; + icon: string; +} + +// Configuration des rôles +const ROLE_CONFIG: Record = { + [UserRole.DCB_ADMIN]: { + label: 'Administrateur DCB', + description: 'Administrateur système avec tous les accès', + badgeClass: 'bg-danger', + icon: 'lucideShield' + }, + [UserRole.DCB_SUPPORT]: { + label: 'Support DCB', + description: 'Support technique avec accès étendus', + badgeClass: 'bg-info', + icon: 'lucideHeadphones' + }, + [UserRole.DCB_PARTNER_ADMIN]: { + label: 'Admin Partenaire', + description: 'Administrateur de partenaire marchand', + badgeClass: 'bg-warning', + icon: 'lucideShieldCheck' + }, + [UserRole.DCB_PARTNER_MANAGER]: { + label: 'Manager Partenaire', + description: 'Manager opérationnel partenaire', + badgeClass: 'bg-success', + icon: 'lucideUserCog' + }, + [UserRole.DCB_PARTNER_SUPPORT]: { + label: 'Support Partenaire', + description: 'Support technique partenaire', + badgeClass: 'bg-secondary', + icon: 'lucideHeadphones' + }, + [UserRole.MERCHANT_CONFIG_ADMIN]: { + label: 'Admin Marchand', + description: 'Administrateur de configuration marchand', + badgeClass: 'bg-warning', + icon: 'lucideSettings' + }, + [UserRole.MERCHANT_CONFIG_MANAGER]: { + label: 'Manager Marchand', + description: 'Manager de configuration marchand', + badgeClass: 'bg-success', + icon: 'lucideUserCog' + }, + [UserRole.MERCHANT_CONFIG_TECHNICAL]: { + label: 'Technique Marchand', + description: 'Support technique configuration marchand', + badgeClass: 'bg-secondary', + icon: 'lucideWrench' + }, + [UserRole.MERCHANT_CONFIG_VIEWER]: { + label: 'Visualiseur Marchand', + description: 'Visualiseur de configuration marchand', + badgeClass: 'bg-light', + icon: 'lucideEye' + } +} as const; + +// Rôles Hub (pour les filtres) +const HUB_ROLES = [ + UserRole.DCB_ADMIN, + UserRole.DCB_SUPPORT, +] as const; + +// Rôles Marchands (pour les filtres) +const MERCHANT_ROLES = [ + UserRole.DCB_PARTNER_ADMIN, + UserRole.DCB_PARTNER_MANAGER, + UserRole.DCB_PARTNER_SUPPORT, + UserRole.MERCHANT_CONFIG_ADMIN, + UserRole.MERCHANT_CONFIG_MANAGER, + UserRole.MERCHANT_CONFIG_TECHNICAL, + UserRole.MERCHANT_CONFIG_VIEWER +] as const; + @Injectable({ providedIn: 'root' }) export class RoleManagementService { private currentRole: UserRole | null = null; @@ -198,14 +280,27 @@ export class RoleManagementService { // === UTILITAIRES === - getRoleLabel(role?: UserRole): string { - const targetRole = role || this.currentRole; - return targetRole ? this.roleLabels[targetRole] || targetRole : ''; + /** + * Méthodes d'utilité pour les rôles + */ + getRoleLabel(role: string): string { + const userRole = role as UserRole; + return ROLE_CONFIG[userRole]?.label || role; } - getRoleIcon(role?: UserRole): string { - const targetRole = role || this.currentRole; - return targetRole ? this.roleIcons[targetRole] || 'user' : 'user'; + getRoleDescription(role: string | UserRole): string { + const userRole = role as UserRole; + return ROLE_CONFIG[userRole]?.description || 'Description non disponible'; + } + + getRoleBadgeClass(role: string): string { + const userRole = role as UserRole; + return ROLE_CONFIG[userRole]?.badgeClass || 'bg-secondary'; + } + + getRoleIcon(role: string): string { + const userRole = role as UserRole; + return ROLE_CONFIG[userRole]?.icon || 'lucideUser'; } getAllRoles(): UserRole[] { diff --git a/src/app/core/services/menu.service.ts b/src/app/core/services/menu.service.ts index 56d50c7..cf4cc74 100644 --- a/src/app/core/services/menu.service.ts +++ b/src/app/core/services/menu.service.ts @@ -77,34 +77,10 @@ export class MenuService { icon: 'lucideCreditCard', url: '/transactions', }, - { - label: 'Opérateurs', - icon: 'lucideServer', - isCollapsed: true, - children: [ - { label: 'Paramètres d\'Intégration', url: '/operators/config' }, - { label: 'Performance & Monitoring', url: '/operators/stats' }, - ], - }, - - { - label: 'Webhooks', - icon: 'lucideShare', - isCollapsed: true, - children: [ - { label: 'Historique', url: '/webhooks/history' }, - { label: 'Statut des Requêtes', url: '/webhooks/status' }, - { label: 'Relancer Webhook', url: '/webhooks/retry' }, - ], - }, { label: 'Abonnements', isTitle: true }, { label: 'Gestion des Abonnements', icon: 'lucideRepeat', url: '/subscriptions' }, - { label: 'Abonnements par Merchant', icon: 'lucideStore', url: '/subscriptions/merchant' }, - - { label: 'Paiements', isTitle: true }, - { label: 'Historique des Paiements', icon: 'lucideCreditCard', url: '/subscriptions/payments' }, - + { label: 'Utilisateurs & Sécurité', isTitle: true }, { label: 'Utilisateurs Hub', @@ -119,8 +95,6 @@ export class MenuService { { label: 'Configurations', isTitle: true }, { label: 'Merchant Config', icon: 'lucideStore', url: '/merchant-config' }, - { label: 'Paramètres Système', icon: 'lucideSettings', url: '/settings' }, - { label: 'Intégrations Externes', icon: 'lucidePlug', url: '/integrations' }, { label: 'Support & Profil', isTitle: true }, { label: 'Support', icon: 'lucideLifeBuoy', url: '/support' }, diff --git a/src/app/core/services/permissions.service.ts b/src/app/core/services/permissions.service.ts index 626a9f0..26b164e 100644 --- a/src/app/core/services/permissions.service.ts +++ b/src/app/core/services/permissions.service.ts @@ -33,25 +33,7 @@ export class PermissionsService { module: 'merchant-users-management', roles: this.allRoles, }, - // Operators - Admin seulement - { - module: 'operators', - roles: [UserRole.DCB_ADMIN], - children: { - 'config': [UserRole.DCB_ADMIN], - 'stats': [UserRole.DCB_ADMIN] - } - }, - // Webhooks - Admin et Partner - { - module: 'webhooks', - roles: [UserRole.DCB_ADMIN, UserRole.DCB_PARTNER_ADMIN], - children: { - 'history': [UserRole.DCB_ADMIN, UserRole.DCB_PARTNER_ADMIN], - 'status': [UserRole.DCB_ADMIN, UserRole.DCB_PARTNER_ADMIN], - 'retry': [UserRole.DCB_ADMIN] - } - }, + // Settings - Tout le monde { module: 'settings', @@ -63,16 +45,6 @@ export class PermissionsService { module: 'subscriptions', roles: this.allRoles }, - { - module: 'subscriptions-merchant', - roles: this.allRoles - }, - - // Payments - { - module: 'subscriptions-payments', - roles: this.allRoles - }, // Settings - Tout le monde { @@ -80,11 +52,6 @@ export class PermissionsService { roles: this.allRoles }, - // Integrations - Admin seulement - { - module: 'integrations', - roles: [UserRole.DCB_ADMIN] - }, // Modules publics - Tout le monde { module: 'support', diff --git a/src/app/layouts/components/data.ts b/src/app/layouts/components/data.ts index 4d586d8..ab90a75 100644 --- a/src/app/layouts/components/data.ts +++ b/src/app/layouts/components/data.ts @@ -59,49 +59,11 @@ export const menuItems: MenuItemType[] = [ icon: 'lucideCreditCard', url: '/transactions', }, - { - label: 'Opérateurs', - icon: 'lucideServer', - isCollapsed: true, - children: [ - { label: 'Paramètres d’Intégration', url: '/operators/config' }, - { label: 'Performance & Monitoring', url: '/operators/stats' }, - ], - }, - - // --------------------------- - // Notifications & Communication - // --------------------------- - { label: 'Communication', isTitle: true }, - { - label: 'Notifications', - icon: 'lucideBell', - isCollapsed: true, - children: [ - { label: 'Liste des Notifications', url: '/notifications/list' }, - { label: 'Filtrage par Type', url: '/notifications/filters' }, - { label: 'Actions Automatiques', url: '/notifications/actions' }, - ], - }, - { - label: 'Webhooks', - icon: 'lucideShare', - isCollapsed: true, - children: [ - { label: 'Historique', url: '/webhooks/history' }, - { label: 'Statut des Requêtes', url: '/webhooks/status' }, - { label: 'Relancer Webhook', url: '/webhooks/retry' }, - ], - }, { label: 'Abonnements', isTitle: true }, { label: 'Gestion des Abonnements', icon: 'lucideRepeat', url: '/subscriptions' }, { label: 'Abonnements par Merchant', icon: 'lucideStore', url: '/subscriptions/merchant' }, - { label: 'Paiements', isTitle: true }, - { label: 'Historique des Paiements', icon: 'lucideCreditCard', url: '/subscriptions/payments' }, - - // --------------------------- // Utilisateurs & Sécurité // --------------------------- @@ -128,7 +90,6 @@ export const menuItems: MenuItemType[] = [ { label: 'Configurations', isTitle: true }, { label: 'Merchant Config', icon: 'lucideStore', url: '/merchant-config' }, { label: 'Paramètres Système', icon: 'lucideSettings', url: '/settings' }, - { label: 'Intégrations Externes', icon: 'lucidePlug', url: '/integrations' }, // --------------------------- // Support & Profil diff --git a/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.html b/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.html index e1fcd11..86a898b 100644 --- a/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.html +++ b/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.html @@ -7,471 +7,633 @@
-
- -
-
-
-

- - Dashboard FinTech Reporting - - -
- Merchant ID: {{ merchantId }} -
-
- Aucun merchant sélectionné -
-
- - - Hub Admin - -

-

- - {{ currentRoleLabel }} - {{ getCurrentMerchantName() }} -

-
-
- -
- - - - - +
+
+ Chargement... +
+

Initialisation du dashboard...

+
+ + +
+ +
+
+
+

+ + Dashboard FinTech Reporting + + +
+ Merchant ID: {{ merchantId }} +
+
+ Aucun merchant sélectionné +
+
+ + + Hub Admin + +

+

+ + {{ currentRoleLabel }} - {{ getCurrentMerchantName() }} +

- - -
-
- -
- - - - - -
+
+ +
+ + - -
- - {{ getCurrentMerchantName() }} -
+ +
-
- - - -
-
-
- - -
-
- -
-
- - {{ currentRoleLabel }} -
-
- - -
-
- - {{ getCurrentMerchantName() }} -
-
- -
-
- - Mis à jour: {{ lastUpdated | date:'HH:mm:ss' }} -
-
- - -
-
- - Services: {{ stats.onlineServices }}/{{ stats.totalServices }} en ligne -
-
- - -
-
- - Merchant ID: {{ merchantId }} -
-
- - -
-
- - Merchant ID: {{ merchantId }} -
-
-
-
- - -
-
- -
- Permissions insuffisantes -
Vous n'avez pas les permissions nécessaires pour voir les données.
-
-
-
- - -
-
- -
- {{ syncResponse.message }} -
Synchronisée à {{ formatDate(syncResponse.timestamp) }}
-
- -
-
- - -
-
- -
-
-
-
-
-
Transactions
-

{{ formatNumber(getPaymentStats().daily.transactions) }}

- Journalier -
-
- -
-
-
- {{ formatCurrency(getPaymentStats().daily.revenue) }} - - - {{ getPaymentStats().daily.successRate | number:'1.0-0' }}% - -
-
-
-
- - -
-
-
-
-
-
Transactions
-

{{ formatNumber(getPaymentStats().weekly.transactions) }}

- Hebdomadaire -
-
- -
-
-
- {{ formatCurrency(getPaymentStats().weekly.revenue) }} - - - {{ getPaymentStats().weekly.successRate | number:'1.0-0' }}% - -
-
-
-
- - -
-
-
-
-
-
Transactions
-

{{ formatNumber(getPaymentStats().monthly.transactions) }}

- Mensuel -
-
- -
-
-
- {{ formatCurrency(getPaymentStats().monthly.revenue) }} - - - {{ getPaymentStats().monthly.successRate | number:'1.0-0' }}% - -
-
-
-
- - -
-
-
-
-
-
Revenue {{ currentYear }}
-

{{ formatCurrency(stats.yearlyRevenue) }}

- Annuel -
-
- -
-
-
- {{ formatNumber(stats.yearlyTransactions) }} transactions - - - {{ currentYear }} - -
-
-
-
- - -
-
-
-
-
-
Abonnements
-

{{ formatNumber(getSubscriptionStats().active) }}

- Actifs -
-
- -
-
-
- Total: {{ formatNumber(getSubscriptionStats().total) }} - - - +{{ getSubscriptionStats().newToday }} - -
-
-
-
- - -
-
-
-
-
-
Taux de succès
-

{{ stats.successRate | number:'1.1-1' }}%

- Global -
-
- -
-
-
- {{ getPerformanceLabel(stats.successRate) }} - - - {{ stats.avgSuccessRate | number:'1.0-0' }}% cible - -
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
- - {{ getChartTitle(dataSelection.metric) }} - - - Merchant {{ merchantId }} - - + +
+
+ +
+ + + + +
-
- - - - -
- -
- - + + +
+ + {{ getCurrentMerchantName() }}
-
-
-
-

Chargement du graphique...

-
-
- -

Aucune donnée disponible

- + + + -
-
+
- -
-
- -
-
-
-
-
- - Comparaison Hebdo/Mens -
- Dernières 8 périodes + +
+
+ +
+
+ + {{ currentRoleLabel }} +
+
+ + +
+
+ + {{ getCurrentMerchantName() }} +
+
+ +
+
+ + Mis à jour: {{ lastUpdated | date:'HH:mm:ss' }} +
+
+ + +
+
+ + Services: {{ stats.onlineServices }}/{{ stats.totalServices }} en ligne +
+
+ + +
+
+ + Merchant ID: {{ merchantId }} +
+
+ + +
+
+ + Merchant ID: {{ merchantId }} +
+
+
+
+ + +
+
+ +
+ Permissions insuffisantes +
Vous n'avez pas les permissions nécessaires pour voir les données.
+
+
+
+ + +
+
+ +
+ {{ syncResponse.message }} +
Synchronisée à {{ formatDate(syncResponse.timestamp) }}
+
+ +
+
+ + +
+
+ +
+
+
+
+
+
Transactions
+

{{ formatNumber(getPaymentStats().daily.transactions) }}

+ Journalier +
+
+
-
-
- - Données de comparaison indisponibles +
+ {{ formatCurrency(getPaymentStats().daily.revenue) }} + + + {{ getPaymentStats().daily.successRate | number:'1.0-0' }}% + +
+
+
+
+ + +
+
+
+
+
+
Transactions
+

{{ formatNumber(getPaymentStats().weekly.transactions) }}

+ Hebdomadaire
-
- +
+ +
+
+
+ {{ formatCurrency(getPaymentStats().weekly.revenue) }} + + + {{ getPaymentStats().weekly.successRate | number:'1.0-0' }}% + +
+
+
+
+ + +
+
+
+
+
+
Transactions
+

{{ formatNumber(getPaymentStats().monthly.transactions) }}

+ Mensuel +
+
+ +
+
+
+ {{ formatCurrency(getPaymentStats().monthly.revenue) }} + + + {{ getPaymentStats().monthly.successRate | number:'1.0-0' }}% + +
+
+
+
+ + +
+
+
+
+
+
Revenue {{ currentYear }}
+

{{ formatCurrency(stats.yearlyRevenue) }}

+ Annuel +
+
+ +
+
+
+ {{ formatNumber(stats.yearlyTransactions) }} transactions + + + {{ currentYear }} + +
+
+
+
+ + +
+
+
+
+
+
Abonnements
+

{{ formatNumber(getSubscriptionStats().active) }}

+ Actifs +
+
+ +
+
+
+ Total: {{ formatNumber(getSubscriptionStats().total) }} + + + +{{ getSubscriptionStats().newToday }} + +
+
+
+
+ + +
+
+
+
+
+
Taux de succès
+

{{ stats.successRate | number:'1.1-1' }}%

+ Global +
+
+ +
+
+
+ {{ getPerformanceLabel(stats.successRate) }} + + + {{ stats.avgSuccessRate | number:'1.0-0' }}% cible + +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+ + {{ getChartTitle(dataSelection.metric) }} + + + Merchant {{ merchantId }} + + + + Données globales + +
+

Visualisation en temps réel

+
+
+ + + + +
+ +
+ + +
+
+
+
+
+
+

Chargement du graphique...

+
+
+ +

Aucune donnée disponible

+ +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+
+ + Comparaison Hebdo/Mens +
+ Dernières 8 périodes +
+
+
+
+ + Données de comparaison indisponibles +
+
+ +
+
+
+
+ + +
+
+
+
+
+ + Performance +
+ +
+
+
+
+
+ +
+

+ {{ stats.successRate | number:'1.0-0' }}% +

+ Taux de succès +
+
+
+ +
+
+
+
+ {{ formatNumber(getCurrentTransactionData()?.items?.[0]?.successCount || 0) }} +
+ Réussies +
+
+
+
+
+ {{ formatNumber(getCurrentTransactionData()?.items?.[0]?.failedCount || 0) }} +
+ Échouées +
+
+
+
+
+ {{ formatNumber(getCurrentTransactionData()?.items?.[0]?.pendingCount || 0) }} +
+ En attente +
+
+
+
+
+
- -
-
-
-
+ +
+
+
+
+
+
+ + Santé des APIs DCB +
+

Statut en temps réel des services backend

+
+
+
+
+ + {{ overallHealth.message }} +
+
+ +
+
+
+
+
+
+
+
+
+
+
{{ service.service }}
+ {{ service.url }} +
+
+ + {{ service.status }} + + + {{ service.statusCode }} + +
+
+
+
+ + + {{ formatTimeAgo(service.checkedAt) }} + +
+
+ + {{ service.responseTime }} + +
+
+
+ + + {{ service.error }} + +
+
+
+
+
+
+ + + Vérification automatique toutes les 5 minutes + +
+
+
+
+ + +
+
+ +
+
+
+
+
- - Performance + + Transactions récentes + + {{ getCurrentMerchantName() }} +
-
+
+
-
-
-
- -
-

- {{ stats.successRate | number:'1.0-0' }}% -

- Taux de succès -
-
-
- -
-
-
-
- {{ formatNumber(getCurrentTransactionData()?.items?.[0]?.successCount || 0) }} -
- Réussies -
-
-
-
-
- {{ formatNumber(getCurrentTransactionData()?.items?.[0]?.failedCount || 0) }} -
- Échouées -
-
-
-
-
- {{ formatNumber(getCurrentTransactionData()?.items?.[0]?.pendingCount || 0) }} -
- En attente -
-
-
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
PériodeMontantTransactionsSuccès
+
{{ item.period }}
+ {{ isViewingGlobal() ? 'Tous merchants' : 'Merchant ' + merchantId }} +
+
{{ formatCurrency(item.totalAmount) }}
+
+
{{ formatNumber(item.count) }}
+
+ + {{ (item.count > 0 ? (item.successCount / item.count) * 100 : 0) | number:'1.1-1' }}% + +
+ +

Aucune transaction disponible

+
+
+
+
-
-
-
- -
-
-
-
-
-
- - Santé des APIs DCB -
-

Statut en temps réel des services backend

-
-
-
-
- - {{ overallHealth.message }} -
-
- -
-
-
-
-
-
-
-
-
-
-
{{ service.service }}
- {{ service.url }} -
-
- - {{ service.status }} - - - {{ service.statusCode }} - -
+ +
+
+
+
+
+
+ + Alertes système +
+

Notifications en temps réel

-
-
- - - {{ formatTimeAgo(service.checkedAt) }} - -
-
- - {{ service.responseTime }} - -
-
-
- - - {{ service.error }} - -
-
-
-
-
-
- - - Vérification automatique toutes les 5 minutes - -
-
-
-
- - -
-
- -
-
-
-
-
-
- - Transactions récentes - - {{ getCurrentMerchantName() }} +
+ + {{ alerts.length }} -
-

Dernières 24 heures

+
-
-
+
+
+
+
+
+ + + + +
+
+
+
{{ alert.title }}
+ {{ formatTimeAgo(alert.timestamp) }} +
+

{{ alert.description }}

+
+
+
+
+ +

Aucune alerte active

+ Tous les systèmes fonctionnent normalement +
+
+
+
-
-
- - - - - - - - - - - - - - - - - - - - - -
PériodeMontantTransactionsSuccès
-
{{ item.period }}
- {{ isViewingGlobal() ? 'Tous merchants' : 'Merchant ' + merchantId }} -
-
{{ formatCurrency(item.totalAmount) }}
-
-
{{ formatNumber(item.count) }}
-
- - {{ (item.count > 0 ? (item.successCount / item.count) * 100 : 0) | number:'1.1-1' }}% - -
- -

Aucune transaction disponible

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

Notifications en temps réel

-
-
- - {{ alerts.length }} - -
+ + +
\ No newline at end of file diff --git a/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts b/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts index f2d5fc3..ae021e6 100644 --- a/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts +++ b/src/app/modules/dcb-dashboard/dcb-reporting-dashboard.ts @@ -39,6 +39,7 @@ import { ReportService } from './services/dcb-reporting.service'; import { DashboardAccess, AllowedMerchant, DashboardAccessService } from './services/dashboard-access.service'; import { AuthService } from '@core/services/auth.service'; import { PageTitle } from '@app/components/page-title/page-title'; +import { RouterModule } from '@angular/router'; // ============ TYPES ET INTERFACES ============ @@ -138,7 +139,9 @@ interface SubscriptionStats { FormsModule, NgIconComponent, NgbDropdownModule, - PageTitle], + PageTitle, + RouterModule + ], providers: [ provideIcons({ lucideActivity, lucideAlertCircle, lucideCheckCircle2, lucideRefreshCw, @@ -309,103 +312,100 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit { Chart.register(...registerables); } - 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(); - }, 100); - - // 3. Charger les merchants (avec délai) - setTimeout(() => { - this.loadAllowedMerchants(); - }, 150); - - if (this.accessService.shouldShowSystemHealth()) { - setInterval(() => { - this.checkSystemHealth(); - }, 5 * 60 * 1000); - } -} - + dashboardInitialized = false; // ============ INITIALISATION ============ - private initializeAccess(): void { - // Attendre que l'accès soit prêt + ngOnInit(): void { + console.log('🔍 Dashboard: ngOnInit() appelé'); + + // Attendre que le DashboardAccessService soit VRAIMENT prêt this.subscriptions.push( - this.accessService.waitForAccess().subscribe(() => { - this.access = this.accessService.getDashboardAccess(); - this.currentRoleLabel = this.access.roleLabel; - this.currentRoleIcon = this.access.roleIcon; - - console.log('✅ Dashboard initialisé avec:', { - access: this.access, - merchantId: this.access.merchantId, - isHubUser: this.access.isHubUser, - }); - - // 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é)'); - } + this.accessService.waitForReady().subscribe({ + next: () => { + console.log('✅ Dashboard: waitForReady() a émis - Initialisation...'); + this.dashboardInitialized = true; + this.initializeDashboard(); + }, + error: (err) => { + console.error('❌ Dashboard: Erreur dans waitForReady():', err); + // Gérer l'erreur - peut-être rediriger vers une page d'erreur + this.addAlert('danger', 'Erreur d\'initialisation', + 'Impossible de charger les informations d\'accès', 'Maintenant'); } }) ); } - - isValidMerchantId(id: any): boolean { - if (id === null || id === undefined) { - return false; - } + private initializeDashboard(): void { + console.log('🚀 Dashboard: initializeDashboard() appelé'); - const numId = Number(id); - return !isNaN(numId) && Number.isInteger(numId) && numId > 0; + try { + // 1. Récupérer l'accès + this.access = this.accessService.getDashboardAccess(); + + console.log('✅ Dashboard: Accès déterminé avec succès:', { + isHubUser: this.access.isHubUser, + merchantId: this.access.merchantId, + role: this.access.userRole, + profile: this.authService.getCurrentUserProfile() + }); + + // 2. Configurer les paramètres + if (this.access.isMerchantUser) { + if (this.access.merchantId) { + this.merchantId = this.access.merchantId; + this.isViewingGlobalData = false; + this.dataSelection.merchantPartnerId = this.merchantId; + console.log(`✅ Dashboard: Merchant User avec ID: ${this.merchantId}`); + } else { + console.error('❌ Dashboard: Merchant User sans merchantId!'); + this.addAlert('danger', 'Erreur de configuration', + 'Impossible de déterminer le merchant ID', 'Maintenant'); + return; + } + } 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(`✅ Dashboard: Hub User avec merchant sélectionné: ${this.merchantId}`); + } else { + this.isViewingGlobalData = true; + this.merchantId = undefined; + this.dataSelection.merchantPartnerId = undefined; + console.log('✅ Dashboard: Hub User en mode global'); + } + } + + // 3. Charger les merchants + this.loadAllowedMerchants(); + + // 4. Charger les données + setTimeout(() => { + this.loadData(); + }, 100); + + // 5. Vérifier la santé du système + if (this.accessService.shouldShowSystemHealth()) { + this.checkSystemHealth(); + } + + } catch (error) { + console.error('❌ Dashboard: Erreur lors de l\'initialisation:', error); + this.addAlert('danger', 'Erreur d\'initialisation', + 'Impossible de configurer le dashboard', 'Maintenant'); + } + } + + private loadData(): void { + if (this.access.isHubUser && this.isViewingGlobalData) { + this.loadGlobalData(); + } else { + this.loadMerchantData(this.merchantId); + } } private loadAllowedMerchants(): void { @@ -423,30 +423,6 @@ 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(); - } else { - this.loadMerchantData(this.merchantId); - } - } else { - this.loadMerchantData(this.merchantId); - } - - if (this.accessService.shouldShowSystemHealth()) { - this.checkSystemHealth(); - } - } - // ============ CHARGEMENT DES DONNÉES ============ private loadGlobalData(): void { if (!this.access.isHubUser) return; @@ -485,25 +461,11 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit { private loadMerchantData(merchantId: number | undefined): void { console.log('Chargement des données pour merchant:', merchantId); - // Vérification plus robuste if (!merchantId || merchantId <= 0 || isNaN(merchantId)) { 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; - } - } else { - return; - } + return; } this.loading.merchantData = true; @@ -1507,6 +1469,15 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit { } // ============ MÉTHODES SPÉCIFIQUES AU CONTEXTE ============ + isValidMerchantId(id: any): boolean { + if (id === null || id === undefined) { + return false; + } + + const numId = Number(id); + return !isNaN(numId) && Number.isInteger(numId) && numId > 0; + } + isViewingGlobal(): boolean { return this.isViewingGlobalData; } @@ -1525,7 +1496,6 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit { } private getCurrentMerchantPartnerId(): string | null { - // Utiliser une valeur par défaut sécurisée return this.access?.merchantId?.toString() || null; } 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 192d5d5..b81ebf2 100644 --- a/src/app/modules/dcb-dashboard/services/dashboard-access.service.ts +++ b/src/app/modules/dcb-dashboard/services/dashboard-access.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Observable, of, BehaviorSubject } from 'rxjs'; -import { map, catchError, switchMap, take, filter, first } from 'rxjs/operators'; +import { map, catchError, switchMap, take, filter, tap } 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'; @@ -8,8 +8,6 @@ import { AuthService } from '@core/services/auth.service'; export interface DashboardAccess { isHubUser: boolean; isMerchantUser: boolean; - roleLabel: string; - roleIcon: string; userRole: UserRole; merchantId?: number; } @@ -24,130 +22,124 @@ export class DashboardAccessService { private accessCache: DashboardAccess | null = null; private merchantsCache: AllowedMerchant[] | null = null; private currentMerchantId: number | null = null; - private accessReady$ = new BehaviorSubject(false); + private ready$ = new BehaviorSubject(false); + private profileLoaded = false; constructor( private roleService: RoleManagementService, private merchantService: MerchantConfigService, private authService: AuthService ) { - // S'abonner aux changements du profil utilisateur - this.initializeProfileSubscription(); + // Initialisation simple + this.initialize(); } - /** - * 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); + private initialize(): void { + console.log('🚀 DashboardAccessService: Initialisation'); + + // S'abonner aux changements de profil + this.authService.getUserProfile().subscribe({ + next: (profile) => { + if (profile) { + console.log('✅ DashboardAccessService: Profil chargé', { + username: profile.username, + merchantPartnerId: profile.merchantPartnerId, + userType: profile.userType + }); + this.profileLoaded = true; + this.ready$.next(true); + } + }, + error: (err) => { + console.error('❌ DashboardAccessService: Erreur de profil:', err); + this.profileLoaded = false; + this.ready$.next(false); + } + }); + + // Nettoyer à la déconnexion + this.authService.getAuthState().subscribe(isAuthenticated => { + if (!isAuthenticated) { + console.log('🚨 DashboardAccessService: Déconnexion détectée'); + this.clearCache(); + this.profileLoaded = false; + this.ready$.next(false); + } }); } - /** - * Attend que l'accès soit prêt - */ - waitForAccess(): Observable { - return this.accessReady$.pipe( - filter(ready => ready), - take(1) + // Attendre que le service soit prêt AVEC PROFIL CHARGÉ + waitForReady(): Observable { + return this.ready$.pipe( + filter(ready => ready && this.profileLoaded), + take(1), + tap(() => { + console.log('✅ DashboardAccessService: waitForReady() - Service vraiment prêt'); + }) ); } - /** - * Obtient l'accès dashboard (version synchrone) - */ + // Obtenir l'accès dashboard getDashboardAccess(): DashboardAccess { if (this.accessCache) { return this.accessCache; } + // VÉRIFIER que le profil est chargé + if (!this.profileLoaded) { + console.warn('⚠️ DashboardAccessService: Tentative d\'accès avant chargement du profil'); + throw new Error('Profil non chargé'); + } + + const profile = this.authService.getCurrentUserProfile(); + + // VÉRIFIER que le profil existe + if (!profile) { + console.error('❌ DashboardAccessService: Profil null dans getDashboardAccess()'); + throw new Error('Profil utilisateur non disponible'); + } + + console.log('📊 DashboardAccessService: getDashboardAccess() avec profil:', { + username: profile.username, + merchantPartnerId: profile.merchantPartnerId, + userType: profile.userType + }); + const userRole = this.roleService.getCurrentRole(); const isHubUser = this.roleService.isHubUser(); let merchantId: number | undefined = undefined; - if (!isHubUser) { - merchantId = this.getMerchantIdForCurrentUser(); + if (!isHubUser && profile.merchantPartnerId) { + merchantId = Number(profile.merchantPartnerId); + if (isNaN(merchantId) || merchantId <= 0) { + console.warn(`⚠️ DashboardAccessService: merchantPartnerId invalide: ${profile.merchantPartnerId}`); + merchantId = undefined; + } } - const access: DashboardAccess = { + this.accessCache = { isHubUser, isMerchantUser: !isHubUser, - roleLabel: this.roleService.getRoleLabel(), - roleIcon: this.roleService.getRoleIcon(), userRole: userRole || UserRole.DCB_SUPPORT, merchantId }; - console.log('📊 DashboardAccess créé:', { - ...access, - merchantId, - userRoleLabel: userRole - }); + console.log('🎯 DashboardAccessService: Accès créé:', this.accessCache); - this.accessCache = access; - return access; + return this.accessCache; } - /** - * Obtient l'accès dashboard (version asynchrone) - */ + // Obtenir l'accès dashboard avec attente getDashboardAccessAsync(): Observable { - return this.waitForAccess().pipe( + return this.waitForReady().pipe( map(() => this.getDashboardAccess()) ); } - /** - * 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 - */ + // Obtenir les marchands disponibles getAvailableMerchants(): Observable { - return this.waitForAccess().pipe( + return this.waitForReady().pipe( switchMap(() => { if (this.merchantsCache) { return of(this.merchantsCache); @@ -155,162 +147,71 @@ export class DashboardAccessService { 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(); + return this.merchantService.getAllMerchants().pipe( + map(merchants => { + const available: AllowedMerchant[] = merchants.map(m => ({ + id: m.id, + name: m.name + })); + + // Option globale pour les hub users + available.unshift({ id: 0, name: '🌐 Données globales' }); + + this.merchantsCache = available; + return available; + }), + catchError(() => { + return of([{ id: 0, name: '🌐 Données globales' }]); + }) + ); } else { - // Merchant users: seulement leur merchant - return this.loadSingleMerchantForUser(access.merchantId); + // Merchant user: seulement son merchant + const merchantId = access.merchantId; + if (merchantId) { + const merchants = [{ id: merchantId, name: `🏪 Merchant ${merchantId}` }]; + this.merchantsCache = merchants; + return of(merchants); + } + return of([]); } }) ); } - /** - * 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) - */ + // Définir le marchand sélectionné (hub users seulement) setSelectedMerchantId(merchantId: number): void { 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'); } } - /** - * Obtient le merchant sélectionné - */ + // Obtenir le marchand sélectionné getSelectedMerchantId(): number | null { const access = this.getDashboardAccess(); if (access.isMerchantUser) { - // Merchant users: toujours leur merchant return access.merchantId || null; } - // Hub users: le merchant sélectionné return this.currentMerchantId; } - /** - * Vérifie si un merchant est accessible - */ - canAccessMerchant(merchantId: number): Observable { - const access = this.getDashboardAccess(); - - if (access.isHubUser) { - return of(true); // Hub users: accès à tous - } - - // Merchant users: seulement leur merchant - return of(access.merchantId === merchantId); - } - - /** - * Nettoie le cache - */ + // Nettoyer le cache clearCache(): void { this.accessCache = null; this.merchantsCache = null; this.currentMerchantId = null; - console.log('🗑️ DashboardAccessService: Cache nettoyé'); } - /** - * Méthode de débogage - */ - 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 ============ + // ============ MÉTHODES UTILITAIRES SIMPLES ============ shouldShowSystemHealth(): boolean { return this.getDashboardAccess().isHubUser; } - shouldShowAllTransactions(): boolean { - return this.getDashboardAccess().isHubUser; + shouldShowAlerts(): boolean { + return true; } canTriggerSync(): boolean { @@ -323,29 +224,17 @@ export class DashboardAccessService { return access.isHubUser && access.userRole === UserRole.DCB_ADMIN; } - shouldShowTransactions(): boolean { - return true; - } - - shouldShowCharts(): boolean { - return true; - } - - shouldShowKPIs(): boolean { - return true; - } - - shouldShowAlerts(): boolean { - return true; - } - - canRefreshData(): boolean { - return true; - } - canSelectMerchant(): boolean { return this.getDashboardAccess().isHubUser; } + + canEditMerchantFilter(): boolean { + const access = this.getDashboardAccess(); + if (access.isHubUser) { + return access.userRole === UserRole.DCB_ADMIN; + } + return access.userRole === UserRole.DCB_PARTNER_ADMIN; + } shouldShowMerchantId(): boolean { const access = this.getDashboardAccess(); @@ -353,26 +242,10 @@ export class DashboardAccessService { (access.isHubUser && this.getSelectedMerchantId() !== null); } - canEditMerchantFilter(): boolean { - const access = this.getDashboardAccess(); - if (access.isHubUser) { - return access.userRole === UserRole.DCB_ADMIN; - } - 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) { @@ -381,9 +254,6 @@ export class DashboardAccessService { return false; } - /** - * Retourne le nom du merchant courant - */ getCurrentMerchantName(): string { const access = this.getDashboardAccess(); @@ -401,14 +271,4 @@ export class DashboardAccessService { 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 diff --git a/src/app/modules/hub-users-management/hub-users-list/hub-users-list.ts b/src/app/modules/hub-users-management/hub-users-list/hub-users-list.ts index a18e5fe..2a9504e 100644 --- a/src/app/modules/hub-users-management/hub-users-list/hub-users-list.ts +++ b/src/app/modules/hub-users-management/hub-users-list/hub-users-list.ts @@ -15,7 +15,7 @@ import { } from '@core/models/dcb-bo-hub-user.model'; import { HubUsersService } from '../hub-users.service'; -import { RoleManagementService } from '@core/services/hub-users-roles-management-old.service'; +import { RoleManagementService } from '@core/services/hub-users-roles-management.service'; import { AuthService } from '@core/services/auth.service'; import { UiCard } from '@app/components/ui-card'; diff --git a/src/app/modules/hub-users-management/hub-users-profile/hub-users-profile.ts b/src/app/modules/hub-users-management/hub-users-profile/hub-users-profile.ts index af4d044..2a2b6ac 100644 --- a/src/app/modules/hub-users-management/hub-users-profile/hub-users-profile.ts +++ b/src/app/modules/hub-users-management/hub-users-profile/hub-users-profile.ts @@ -14,7 +14,7 @@ import { } from '@core/models/dcb-bo-hub-user.model'; import { HubUsersService } from '../hub-users.service'; -import { RoleManagementService } from '@core/services/hub-users-roles-management-old.service'; +import { RoleManagementService } from '@core/services/hub-users-roles-management.service'; import { AuthService } from '@core/services/auth.service'; @Component({ @@ -214,12 +214,6 @@ export class HubUserProfile implements OnInit, OnDestroy { return; } - // Vérifier que l'utilisateur peut attribuer ce rôle - if (!this.roleService.canAssignRole(this.currentUserRole, newRole)) { - this.error = 'Vous n\'avez pas la permission d\'attribuer ce rôle'; - return; - } - // Vérifier que le rôle est valide pour les utilisateurs Hub if (!this.isValidHubRole(newRole)) { this.error = 'Rôle invalide pour un utilisateur Hub'; @@ -310,7 +304,8 @@ export class HubUserProfile implements OnInit, OnDestroy { } // Pour les utilisateurs Hub, utiliser les permissions du service de rôle - return this.roleService.canEditUsers(this.currentUserRole); + + return this.roleService.isAnyAdmin(); } /** @@ -318,7 +313,8 @@ export class HubUserProfile implements OnInit, OnDestroy { */ canManageRoles(): boolean { // Pour les Hub, utiliser les permissions du service de rôle - return this.roleService.canManageRoles(this.currentUserRole); + + return this.roleService.isAnyAdmin(); } /** @@ -331,7 +327,8 @@ export class HubUserProfile implements OnInit, OnDestroy { } // Pour les Hub, utiliser les permissions du service de rôle - return this.roleService.canEditUsers(this.currentUserRole); + + return this.roleService.isAnyAdmin(); } /** @@ -344,7 +341,8 @@ export class HubUserProfile implements OnInit, OnDestroy { } // Pour les Hub, utiliser les permissions générales - return this.roleService.canEditUsers(this.currentUserRole); + + return this.roleService.isAnyAdmin(); } /** @@ -357,7 +355,8 @@ export class HubUserProfile implements OnInit, OnDestroy { } // Pour les Hub, utiliser les permissions du service de rôle - return this.roleService.canDeleteUsers(this.currentUserRole); + + return this.roleService.isAnyAdmin(); } // ==================== UTILITAIRES D'AFFICHAGE ==================== @@ -526,8 +525,7 @@ export class HubUserProfile implements OnInit, OnDestroy { } getAssignableRoles(): UserRole[] { - const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT]; - return hubRoles.filter(role => this.roleService.canAssignRole(this.currentUserRole, role)); + return [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT]; } // Méthodes pour les actions spécifiques diff --git a/src/app/modules/hub-users-management/hub-users.ts b/src/app/modules/hub-users-management/hub-users.ts index 1b8acf4..5115265 100644 --- a/src/app/modules/hub-users-management/hub-users.ts +++ b/src/app/modules/hub-users-management/hub-users.ts @@ -6,7 +6,7 @@ import { NgbNavModule, NgbModal, NgbModalModule } from '@ng-bootstrap/ng-bootstr import { Subject, takeUntil } from 'rxjs'; import { HubUsersService } from './hub-users.service'; -import { RoleManagementService } from '@core/services/hub-users-roles-management-old.service'; +import { RoleManagementService } from '@core/services/hub-users-roles-management.service'; import { AuthService } from '@core/services/auth.service'; import { MerchantSyncService } from './merchant-sync-orchestrator.service'; import { PageTitle } from '@app/components/page-title/page-title'; @@ -145,13 +145,7 @@ export class HubUsersManagement implements OnInit, OnDestroy { console.log(`HUB User ROLE: ${this.currentUserRole}`); if (this.currentUserRole) { - this.roleService.setCurrentUserRole(this.currentUserRole); - this.userPermissions = this.roleService.getPermissionsForRole(this.currentUserRole); - this.canCreateUsers = this.roleService.canCreateUsers(this.currentUserRole); - this.canDeleteUsers = this.roleService.canDeleteUsers(this.currentUserRole); - this.canManageRoles = this.roleService.canManageRoles(this.currentUserRole); - - this.assignableRoles = this.roleService.getAssignableRoles(this.currentUserRole); + this.roleService.setCurrentRole(this.currentUserRole); console.log('Assignable roles:', this.assignableRoles); } }, @@ -176,14 +170,8 @@ export class HubUsersManagement implements OnInit, OnDestroy { /** * Fallback en cas d'erreur de chargement du profil */ - private fallbackPermissions(): void { - this.currentUserRole = this.authService.getCurrentUserRole(); - - if (this.currentUserRole) { - this.canCreateUsers = this.roleService.canCreateUsers(this.currentUserRole); - this.canDeleteUsers = this.roleService.canDeleteUsers(this.currentUserRole); - this.canManageRoles = this.roleService.canManageRoles(this.currentUserRole); - } + private fallbackPermissions(): boolean { + return this.roleService.isAnyAdmin(); } /** @@ -541,7 +529,7 @@ export class HubUsersManagement implements OnInit, OnDestroy { * Vérifie si l'utilisateur peut attribuer un rôle spécifique */ canAssignRole(targetRole: UserRole): boolean { - return this.roleService.canAssignRole(this.currentUserRole, targetRole); + return this.roleService.isAnyAdmin(); } // Réinitialiser le mot de passe diff --git a/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.html b/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.html index 2d4e83a..4efb967 100644 --- a/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.html +++ b/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.html @@ -22,7 +22,7 @@ [class.active]="roleFilter === 'all'" (click)="filterByRole('all')" > - Tous ({{ getTotalUsersCount() }}) + Tous ({{ userStats?.total }})
- @if (totalPages > 1) { + @if (totalPages >= 1) {
- Affichage de {{ getStartIndex() }} à {{ getEndIndex() }} sur {{ totalItems }} utilisateurs + Affichage de {{ getStartIndex() }} + à {{ getEndIndex() }} + sur {{ totalItems }} utilisateurs