// src/app/modules/merchant-users/list/list.ts import { Component, inject, OnInit, Output, EventEmitter, ChangeDetectorRef, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgIcon } from '@ng-icons/core'; import { NgbPaginationModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { catchError, map, of, Subject, takeUntil } from 'rxjs'; import { MerchantUserDto, PaginatedUserResponse, SearchUsersParams, UserRole, UserType } from '@core/models/dcb-bo-hub-user.model'; import { MerchantUsersService } from '../services/merchant-users.service'; import { HubUsersService } from '../../hub-users/services/hub-users.service'; import { AuthService } from '@core/services/auth.service'; import { UiCard } from '@app/components/ui-card'; @Component({ selector: 'app-merchant-users-list', standalone: true, imports: [ CommonModule, FormsModule, NgIcon, UiCard, NgbPaginationModule, NgbDropdownModule ], templateUrl: './list.html', }) export class MerchantUsersList implements OnInit, OnDestroy { private merchantUsersService = inject(MerchantUsersService); private hubUsersService = inject(HubUsersService); private authService = inject(AuthService); private cdRef = inject(ChangeDetectorRef); private destroy$ = new Subject(); readonly UserRole = UserRole; readonly UserType = UserType; @Output() userSelected = new EventEmitter(); @Output() openCreateModal = new EventEmitter(); @Output() openResetPasswordModal = new EventEmitter(); @Output() openDeleteUserModal = new EventEmitter(); // Données allUsers: MerchantUserDto[] = []; filteredUsers: MerchantUserDto[] = []; displayedUsers: MerchantUserDto[] = []; // États loading = false; error = ''; // Recherche et filtres searchTerm = ''; statusFilter: 'all' | 'enabled' | 'disabled' = 'all'; emailVerifiedFilter: 'all' | 'verified' | 'not-verified' = 'all'; roleFilter: UserRole | 'all' = 'all'; // Pagination currentPage = 1; itemsPerPage = 10; totalItems = 0; totalPages = 0; // Tri sortField: keyof MerchantUserDto = 'username'; sortDirection: 'asc' | 'desc' = 'asc'; // Rôles disponibles pour le filtre availableRoles: { value: UserRole | 'all'; label: string }[] = [ { value: 'all', label: 'Tous les rôles' }, { value: UserRole.DCB_PARTNER_ADMIN, label: 'Administrateurs' }, { value: UserRole.DCB_PARTNER_MANAGER, label: 'Managers' }, { value: UserRole.DCB_PARTNER_SUPPORT, label: 'Support' } ]; // ID du merchant partner courant et permissions currentMerchantPartnerId: string = ''; currentUserRole: UserRole | null = null; isHubAdminOrSupport = false; canViewAllMerchants = false; isDcbPartner = false; ngOnInit() { this.loadCurrentUserPermissions(); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } private loadCurrentUserPermissions() { this.authService.getProfile().subscribe({ next: (user: any) => { // Méthode robuste pour récupérer le rôle this.currentUserRole = this.extractUserRole(user); // Déterminer le type d'utilisateur avec des méthodes plus robustes this.isHubAdminOrSupport = this.isHubAdminOrSupportRole(this.currentUserRole); this.isDcbPartner = this.isDcbPartnerRole(this.currentUserRole); this.canViewAllMerchants = this.canViewAllMerchantsCheck(this.currentUserRole); // Déterminer le merchantPartnerId if(this.isDcbPartner){ this.currentMerchantPartnerId = user.id; } else if(!this.isDcbPartner || !this.isHubAdminOrSupport) { this.currentMerchantPartnerId = this.extractMerchantPartnerId(user); } this.loadUsers(); }, error: (error) => { console.error('❌ Error loading current user permissions:', error); // Fallback: utiliser les méthodes d'AuthService this.fallbackPermissions(); this.loadUsers(); // Charger quand même les utilisateurs } }); } /** * Méthode robuste pour extraire le rôle de l'utilisateur */ private extractUserRole(user: any): UserRole | null { console.log('🔍 Extracting user role from:', user); // 1. Essayer depuis les rôles du token (méthode principale) const tokenRoles = this.authService.getCurrentUserRoles(); if (tokenRoles && tokenRoles.length > 0) { console.log('✅ Role from token:', tokenRoles[0]); return tokenRoles[0]; } // 2. Essayer depuis le profil user.role if (user?.role && Object.values(UserRole).includes(user.role)) { console.log('✅ Role from user.role:', user.role); return user.role as UserRole; } // 3. Essayer depuis user.userType pour déduire le rôle if (user?.userType) { const roleFromType = this.getRoleFromUserType(user.userType); if (roleFromType) { console.log('✅ Role deduced from userType:', roleFromType); return roleFromType; } } // 4. Essayer depuis les attributs étendus if (user?.attributes?.role?.[0]) { const roleFromAttributes = user.attributes.role[0]; if (Object.values(UserRole).includes(roleFromAttributes as UserRole)) { console.log('✅ Role from attributes:', roleFromAttributes); return roleFromAttributes as UserRole; } } console.warn('❌ No valid role found in user profile'); return null; } /** * Déduire le rôle à partir du userType */ private getRoleFromUserType(userType: string): UserRole | null { const typeMapping: { [key: string]: UserRole } = { [UserType.HUB]: UserRole.DCB_ADMIN, // Fallback pour HUB [UserType.MERCHANT]: UserRole.DCB_PARTNER_ADMIN, // Fallback pour MERCHANT }; return typeMapping[userType] || null; } /** * Vérifier si l'utilisateur est Hub Admin ou Support */ private isHubAdminOrSupportRole(role: UserRole | null): boolean { if (!role) return false; const hubAdminSupportRoles = [ UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER // DCB_PARTNER peut aussi être considéré comme Hub ]; return hubAdminSupportRoles.includes(role); } /** * Vérifier si l'utilisateur est DCB Partner */ private isDcbPartnerRole(role: UserRole | null): boolean { if (!role) return false; const partnerRoles = [ UserRole.DCB_PARTNER, UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT ]; return partnerRoles.includes(role); } /** * Vérifier si l'utilisateur peut voir tous les merchants */ private canViewAllMerchantsCheck(role: UserRole | null): boolean { if (!role) return false; const canViewAllRoles = [ UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT ]; return canViewAllRoles.includes(role); } /** * Extraire le merchantPartnerId de manière robuste */ private extractMerchantPartnerId(user: any): string { console.log('🔍 Extracting merchantPartnerId from:', user); // 1. Essayer depuis merchantPartnerId direct if (user?.merchantPartnerId) { console.log('✅ merchantPartnerId from direct property:', user.merchantPartnerId); return user.merchantPartnerId; } // 2. Essayer depuis les attributs if (user?.attributes?.merchantPartnerId?.[0]) { console.log('✅ merchantPartnerId from attributes:', user.attributes.merchantPartnerId[0]); return user.attributes.merchantPartnerId[0]; } // 3. Essayer depuis AuthService const authServiceMerchantId = this.authService.getCurrentMerchantPartnerId(); if (authServiceMerchantId) { console.log('✅ merchantPartnerId from AuthService:', authServiceMerchantId); return authServiceMerchantId; } // 4. Pour les rôles Hub, pas de merchantPartnerId if (this.isHubAdminOrSupport) { console.log('ℹ️ Hub user - no merchantPartnerId'); return ''; } console.warn('❌ No merchantPartnerId found'); return ''; } /** * Fallback en cas d'erreur de chargement du profil */ private fallbackPermissions(): void { console.warn('🔄 Using fallback permissions'); // Utiliser les méthodes d'AuthService comme fallback this.currentUserRole = this.authService.getCurrentUserRole(); this.isHubAdminOrSupport = this.authService.isHubUser() || this.authService.hasRole(UserRole.DCB_ADMIN) || this.authService.hasRole(UserRole.DCB_SUPPORT); this.isDcbPartner = this.authService.isMerchantUser() || this.authService.hasRole(UserRole.DCB_PARTNER); this.canViewAllMerchants = this.authService.canViewAllMerchants(); this.currentMerchantPartnerId = this.authService.getCurrentMerchantPartnerId() || ''; console.log('🔄 Fallback permissions:', { currentUserRole: this.currentUserRole, isHubAdminOrSupport: this.isHubAdminOrSupport, isDcbPartner: this.isDcbPartner, canViewAllMerchants: this.canViewAllMerchants, currentMerchantPartnerId: this.currentMerchantPartnerId }); } loadUsers() { this.loading = true; this.error = ''; let usersObservable; if (this.canViewAllMerchants && !this.currentMerchantPartnerId) { console.log('🔍 Loading ALL merchant users (Hub Admin/Support)'); usersObservable = this.hubUsersService.findAllMerchantUsers(this.currentPage, this.itemsPerPage).pipe( map((response: PaginatedUserResponse) => { console.log('✅ All merchant users loaded:', response.users.length); return response.users as MerchantUserDto[]; }), catchError(error => { console.error('❌ Error loading all merchant users:', error); this.error = 'Erreur lors du chargement de tous les utilisateurs marchands'; return of([]); }) ); } else if (this.currentMerchantPartnerId) { console.log(`🔍 Loading merchant users for partner: ${this.currentMerchantPartnerId}`); usersObservable = this.merchantUsersService.getMerchantUsersByPartner(this.currentMerchantPartnerId).pipe( catchError(error => { console.error('❌ Error loading merchant users by partner:', error); this.error = 'Erreur lors du chargement des utilisateurs du partenaire'; return of([]); }) ); } else { console.log('🔍 Loading my merchant users'); usersObservable = this.merchantUsersService.getMyMerchantUsers(this.currentMerchantPartnerId).pipe( catchError(error => { console.error('❌ Error loading my merchant users:', error); this.error = 'Erreur lors du chargement de mes utilisateurs marchands'; return of([]); }) ); } usersObservable .pipe(takeUntil(this.destroy$)) .subscribe({ next: (users: MerchantUserDto[]) => { this.allUsers = users || []; console.log(`✅ Loaded ${this.allUsers.length} merchant users`); this.applyFiltersAndPagination(); this.loading = false; this.cdRef.detectChanges(); }, error: (error: any) => { this.error = 'Erreur lors du chargement des utilisateurs marchands'; this.loading = false; this.allUsers = []; this.filteredUsers = []; this.displayedUsers = []; this.cdRef.detectChanges(); console.error('❌ Error in users subscription:', error); } }); } // Recherche et filtres onSearch() { this.currentPage = 1; this.applyFiltersAndPagination(); } onClearFilters() { this.searchTerm = ''; this.statusFilter = 'all'; this.emailVerifiedFilter = 'all'; this.roleFilter = 'all'; this.currentPage = 1; this.applyFiltersAndPagination(); } applyFiltersAndPagination() { // Vérifier que allUsers est défini if (!this.allUsers) { this.allUsers = []; } console.log(`🔍 Applying filters to ${this.allUsers.length} users`); // Appliquer les filtres this.filteredUsers = this.allUsers.filter(user => { // Filtre de recherche const matchesSearch = !this.searchTerm || user.username.toLowerCase().includes(this.searchTerm.toLowerCase()) || user.email.toLowerCase().includes(this.searchTerm.toLowerCase()) || (user.firstName && user.firstName.toLowerCase().includes(this.searchTerm.toLowerCase())) || (user.lastName && user.lastName.toLowerCase().includes(this.searchTerm.toLowerCase())); // Filtre par statut const matchesStatus = this.statusFilter === 'all' || (this.statusFilter === 'enabled' && user.enabled) || (this.statusFilter === 'disabled' && !user.enabled); // Filtre par email vérifié const matchesEmailVerified = this.emailVerifiedFilter === 'all' || (this.emailVerifiedFilter === 'verified' && user.emailVerified) || (this.emailVerifiedFilter === 'not-verified' && !user.emailVerified); // Filtre par rôle const matchesRole = this.roleFilter === 'all' || user.role === this.roleFilter; return matchesSearch && matchesStatus && matchesEmailVerified && matchesRole; }); console.log(`✅ Filtered to ${this.filteredUsers.length} users`); // Appliquer le tri this.filteredUsers.sort((a, b) => { const aValue = a[this.sortField]; const bValue = b[this.sortField]; if (aValue === bValue) return 0; let comparison = 0; if (typeof aValue === 'string' && typeof bValue === 'string') { comparison = aValue.localeCompare(bValue); } else if (typeof aValue === 'number' && typeof bValue === 'number') { comparison = aValue - bValue; } else if (typeof aValue === 'boolean' && typeof bValue === 'boolean') { comparison = (aValue === bValue) ? 0 : aValue ? -1 : 1; } return this.sortDirection === 'asc' ? comparison : -comparison; }); // Calculer la pagination this.totalItems = this.filteredUsers.length; this.totalPages = Math.ceil(this.totalItems / this.itemsPerPage); // Appliquer la pagination const startIndex = (this.currentPage - 1) * this.itemsPerPage; const endIndex = startIndex + this.itemsPerPage; this.displayedUsers = this.filteredUsers.slice(startIndex, endIndex); console.log(`📄 Pagination: page ${this.currentPage} of ${this.totalPages}, showing ${this.displayedUsers.length} users`); } // Tri sort(field: keyof MerchantUserDto) { if (this.sortField === field) { this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc'; } else { this.sortField = field; this.sortDirection = 'asc'; } console.log(`🔀 Sorting by ${field} (${this.sortDirection})`); this.applyFiltersAndPagination(); } getSortIcon(field: keyof MerchantUserDto): string { if (this.sortField !== field) return 'lucideArrowUpDown'; return this.sortDirection === 'asc' ? 'lucideArrowUp' : 'lucideArrowDown'; } // Pagination onPageChange(page: number) { console.log(`📄 Changing to page ${page}`); this.currentPage = page; this.applyFiltersAndPagination(); } getStartIndex(): number { return (this.currentPage - 1) * this.itemsPerPage + 1; } getEndIndex(): number { return Math.min(this.currentPage * this.itemsPerPage, this.totalItems); } // Actions viewUserProfile(userId: string) { console.log(`👤 Viewing user profile: ${userId}`); this.userSelected.emit(userId); } // Méthode pour réinitialiser le mot de passe resetPassword(user: MerchantUserDto) { console.log(`🔑 Resetting password for user: ${user.username}`); this.openResetPasswordModal.emit(user.id); } // Méthode pour ouvrir le modal de suppression deleteUser(user: MerchantUserDto) { console.log(`🗑️ Deleting user: ${user.username}`); this.openDeleteUserModal.emit(user.id); } // Activer un utilisateur enableUser(user: MerchantUserDto) { console.log(`✅ Enabling user: ${user.username}`); this.merchantUsersService.enableMerchantUser(user.id) .pipe(takeUntil(this.destroy$)) .subscribe({ next: (updatedUser) => { console.log(`✅ User ${user.username} enabled successfully`); // Mettre à jour l'utilisateur dans la liste const index = this.allUsers.findIndex(u => u.id === user.id); if (index !== -1) { this.allUsers[index] = updatedUser; } this.applyFiltersAndPagination(); this.cdRef.detectChanges(); }, error: (error) => { console.error('❌ Error enabling merchant user:', error); this.error = 'Erreur lors de l\'activation de l\'utilisateur'; this.cdRef.detectChanges(); } }); } // Désactiver un utilisateur disableUser(user: MerchantUserDto) { console.log(`❌ Disabling user: ${user.username}`); this.merchantUsersService.disableMerchantUser(user.id) .pipe(takeUntil(this.destroy$)) .subscribe({ next: (updatedUser) => { console.log(`✅ User ${user.username} disabled successfully`); // Mettre à jour l'utilisateur dans la liste const index = this.allUsers.findIndex(u => u.id === user.id); if (index !== -1) { this.allUsers[index] = updatedUser; } this.applyFiltersAndPagination(); this.cdRef.detectChanges(); }, error: (error) => { console.error('❌ Error disabling merchant user:', error); this.error = 'Erreur lors de la désactivation de l\'utilisateur'; this.cdRef.detectChanges(); } }); } // ==================== UTILITAIRES D'AFFICHAGE ==================== getStatusBadgeClass(user: MerchantUserDto): string { if (!user.enabled) return 'badge bg-danger'; if (!user.emailVerified) return 'badge bg-warning'; return 'badge bg-success'; } getStatusText(user: MerchantUserDto): string { if (!user.enabled) return 'Désactivé'; if (!user.emailVerified) return 'Email non vérifié'; return 'Actif'; } getRoleBadgeClass(role: UserRole): string { switch (role) { case UserRole.DCB_PARTNER_ADMIN: return 'bg-danger'; case UserRole.DCB_PARTNER_MANAGER: return 'bg-warning text-dark'; case UserRole.DCB_PARTNER_SUPPORT: return 'bg-info text-white'; default: return 'bg-secondary'; } } getRoleDisplayName(role: UserRole): string { const roleNames = { [UserRole.DCB_ADMIN]: 'Administrateur', [UserRole.DCB_PARTNER]: 'Manager', [UserRole.DCB_SUPPORT]: 'Support', [UserRole.DCB_PARTNER_ADMIN]: 'Admin Marchand', [UserRole.DCB_PARTNER_MANAGER]: 'Manager Marchand', [UserRole.DCB_PARTNER_SUPPORT]: 'Support Marchand' }; return roleNames[role] || role; } getRoleIcon(role: UserRole): string { switch (role) { case UserRole.DCB_PARTNER_ADMIN: return 'lucideShield'; case UserRole.DCB_PARTNER_MANAGER: return 'lucideUserCog'; case UserRole.DCB_PARTNER_SUPPORT: return 'lucideHeadphones'; default: return 'lucideUser'; } } formatTimestamp(timestamp: number): string { if (!timestamp) return 'Non disponible'; return new Date(timestamp).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } getUserInitials(user: MerchantUserDto): string { return (user.firstName?.charAt(0) || '') + (user.lastName?.charAt(0) || '') || 'U'; } getUserDisplayName(user: MerchantUserDto): string { if (user.firstName && user.lastName) { return `${user.firstName} ${user.lastName}`; } return user.username; } // ==================== STATISTIQUES ==================== getUsersCountByRole(role: UserRole): number { return this.allUsers.filter(user => user.role === role).length; } getEnabledUsersCount(): number { return this.allUsers.filter(user => user.enabled).length; } getDisabledUsersCount(): number { return this.allUsers.filter(user => !user.enabled).length; } getEmailVerifiedCount(): number { return this.allUsers.filter(user => user.emailVerified).length; } getTotalUsersCount(): number { return this.allUsers.length; } // ==================== MÉTHODES UTILITAIRES ==================== hasRole(user: MerchantUserDto, role: UserRole): boolean { return user.role === role; } isAdmin(user: MerchantUserDto): boolean { return this.hasRole(user, UserRole.DCB_PARTNER_ADMIN); } isManager(user: MerchantUserDto): boolean { return this.hasRole(user, UserRole.DCB_PARTNER_MANAGER); } isSupport(user: MerchantUserDto): boolean { return this.hasRole(user, UserRole.DCB_PARTNER_SUPPORT); } // Recherche rapide par rôle filterByRole(role: UserRole | 'all') { this.roleFilter = role; this.currentPage = 1; this.applyFiltersAndPagination(); } // Recherche via le service (pour des recherches plus complexes) searchUsers() { if (this.searchTerm.trim()) { this.loading = true; const searchParams: SearchUsersParams = { query: this.searchTerm, userType: UserType.MERCHANT }; if (this.roleFilter !== 'all') { searchParams.role = this.roleFilter as UserRole; } if (this.statusFilter !== 'all') { searchParams.enabled = this.statusFilter === 'enabled'; } console.log('🔍 Performing advanced search:', searchParams); this.merchantUsersService.searchMerchantUsers(searchParams) .pipe(takeUntil(this.destroy$)) .subscribe({ next: (users) => { this.allUsers = users; console.log(`✅ Advanced search found ${users.length} users`); this.applyFiltersAndPagination(); this.loading = false; this.cdRef.detectChanges(); }, error: (error: any) => { console.error('❌ Error searching users:', error); this.loading = false; this.cdRef.detectChanges(); } }); } else { this.loadUsers(); // Recharger tous les utilisateurs si la recherche est vide } } // Recharger les données refreshData() { console.log('🔄 Refreshing data...'); this.loadUsers(); } }