diff --git a/src/app/core/models/merchant-config.model.ts b/src/app/core/models/merchant-config.model.ts index 18bd09c..24891a1 100644 --- a/src/app/core/models/merchant-config.model.ts +++ b/src/app/core/models/merchant-config.model.ts @@ -63,7 +63,10 @@ export interface MerchantUser { username?: string; email?: string; firstName?: string; - lastName?: string; // Référence au merchant dans MerchantConfig + lastName?: string; + merchantPartnerId: string; + enabled: boolean; + emailVerified: boolean; createdAt?: string; updatedAt?: string; } @@ -212,8 +215,9 @@ export interface MerchantStatsResponse { // === SEARCH === export interface SearchMerchantsParams { query?: string; - page?: number; + take?: number; limit?: number; + page?: number; } // === TYPES POUR GESTION DES RÔLES === diff --git a/src/app/modules/help/help.scss b/src/app/modules/help/help.scss index 6e101e2..4df7a47 100644 --- a/src/app/modules/help/help.scss +++ b/src/app/modules/help/help.scss @@ -459,26 +459,18 @@ $red: #ef4444; } .faq-answer { - $padding-x: 1.25rem; - $padding-y: 1.25rem; - $padding-left: 4rem; - - padding: 0 $padding-x $padding-y $padding-left; + padding: 0 1.25rem 1.25rem 4rem; animation: fadeIn 0.3s ease; p { - $font-size: 0.9375rem; - $line-height: 1.8; - $border-width: 3px; - - font-size: $font-size; + font-size: 0.9375rem; color: $text-secondary; - line-height: $line-height; + line-height: 1.8; margin: 0; padding: 1rem; background: $bg-card; border-radius: 0.75rem; - border-left: $border-width solid $indigo; + border-left: 3px solid $indigo; } } diff --git a/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts b/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts index 0f2cbaa..717a741 100644 --- a/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts +++ b/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts @@ -263,12 +263,12 @@ export class MerchantSyncService { // Récupérer les utilisateurs de MerchantConfig return this.merchantConfigService.getMerchantUsers(Number(merchantConfigId)).pipe( switchMap(merchantConfigUsers => { - if (merchantConfigUsers.length === 0) { + if (merchantConfigUsers.total === 0) { return of([]); } // Récupérer les détails de chaque utilisateur depuis Keycloak - const userObservables = merchantConfigUsers.map(mcUser => + const userObservables = merchantConfigUsers.items.map((mcUser: { userId: string; }) => this.getUserById(mcUser.userId).pipe( catchError(() => of(null)) // Ignorer les utilisateurs non trouvés ) diff --git a/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.ts b/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.ts index 2967b01..c019420 100644 --- a/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.ts +++ b/src/app/modules/hub-users-management/merchant-users-list/merchant-users-list.ts @@ -38,7 +38,6 @@ import { MerchantUser } from '@core/models/merchant-config.model'; export class MerchantUsersList implements OnInit, OnDestroy { private authService = inject(AuthService); private merchantUsersService = inject(MerchantUsersService); - private merchantConfigService = inject(MerchantConfigService); protected roleService = inject(RoleManagementService); private cdRef = inject(ChangeDetectorRef); private destroy$ = new Subject(); diff --git a/src/app/modules/hub-users-management/merchant-users.html b/src/app/modules/hub-users-management/merchant-users.html index f6ddd8c..d96f4fa 100644 --- a/src/app/modules/hub-users-management/merchant-users.html +++ b/src/app/modules/hub-users-management/merchant-users.html @@ -69,8 +69,8 @@ -
  • - +
  • + Profil Utilisateur @@ -121,19 +121,6 @@ } - - @if (!canManageRoles && assignableRoles.length === 1) { -
    - - - Permissions limitées : Vous ne pouvez créer que des utilisateurs avec le rôle - - {{ getRoleLabel(assignableRoles[0]) }} - - -
    - } -
    @@ -335,11 +322,7 @@ }
    - @if (canManageRoles) { Sélectionnez le rôle principal à assigner à cet utilisateur - } @else { - Vous ne pouvez pas modifier les rôles disponibles - }
    @@ -356,24 +339,6 @@ - - @if (!canManageRoles) { -
    -
    -
    - -
    - - Permissions limitées : - Vous ne pouvez créer que des utilisateurs avec des rôles spécifiques. - Seul un DCB Partner peut attribuer tous les rôles. - -
    -
    -
    -
    - } - @if (newUser.role) {
    diff --git a/src/app/modules/hub-users-management/merchant-users.ts b/src/app/modules/hub-users-management/merchant-users.ts index f364228..0a23d9b 100644 --- a/src/app/modules/hub-users-management/merchant-users.ts +++ b/src/app/modules/hub-users-management/merchant-users.ts @@ -20,7 +20,7 @@ import { } from '@core/models/dcb-bo-hub-user.model'; import { MerchantConfigService } from '@modules/merchant-config/merchant-config.service'; import { AddUserToMerchantDto, Merchant } from '@core/models/merchant-config.model'; -import { MerchantSyncService } from './merchant-sync-orchestrator.service'; +import { MerchantConfigsList } from '@modules/merchant-config/merchant-config-list/merchant-config-list'; @Component({ selector: 'app-merchant-users', @@ -56,7 +56,7 @@ export class MerchantUsersManagement implements OnInit, OnDestroy { badge: any = { icon: 'lucideBuilding', text: 'Merchant Users' }; // État de l'interface - activeTab: 'list' | 'profile' = 'list'; + activeTab: 'list' | 'user-profile' = 'list'; selectedUserId: string | null = null; // Gestion des permissions @@ -65,7 +65,6 @@ export class MerchantUsersManagement implements OnInit, OnDestroy { userPermissions: any = null; canCreateUsers = false; canDeleteUsers = false; - canManageRoles = false; // Formulaire de création newUser: { @@ -108,6 +107,7 @@ export class MerchantUsersManagement implements OnInit, OnDestroy { // Références aux composants enfants @ViewChild(MerchantUsersList) merchantUsersList!: MerchantUsersList; + @ViewChild(MerchantConfigsList) merchantConfigList!: MerchantConfigsList; // Rôles disponibles availableRoles: { value: UserRole; label: string; description: string }[] = []; @@ -265,7 +265,7 @@ export class MerchantUsersManagement implements OnInit, OnDestroy { loadingProfiles: { [userId: string]: boolean } = {}; // État de chargement par user // Méthode pour changer d'onglet - showTab(tab: 'list' | 'profile', userId?: string) { + showTab(tab: 'list' | 'user-profile', userId?: string) { console.log(`Switching to tab: ${tab}`, userId ? `for user ${userId}` : ''); this.activeTab = tab; @@ -383,7 +383,7 @@ export class MerchantUsersManagement implements OnInit, OnDestroy { // Méthodes de gestion des événements du composant enfant onUserSelected(userId: string) { - this.showTab('profile', userId); + this.showTab('user-profile', userId); } onResetPasswordRequested(event: any) { diff --git a/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.html b/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.html index b237a40..06ba01f 100644 --- a/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.html +++ b/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.html @@ -9,307 +9,379 @@
    - - -
    -
    -
    - -
    + @if (!shouldDisplayMerchantList()) { +
    + +
    Accès réservé
    +

    Cette section est réservée aux administrateurs Hub.

    +
    + } @else { + +
    +
    +
    + +
    + + +
    + + +
    + +
    +
    +
    + +
    +
    + @if (showCreateButton && canCreateMerchants) { + + }
    -
    -
    - @if (showCreateButton && canCreateMerchants) { - - } - + } +
    +
    + +
    + +
    + +
    +
    -
    - -
    -
    -
    - - - - -
    -
    - -
    - -
    - -
    - -
    -
    - - - @if (loading) { -
    -
    - Chargement... -
    -

    {{ getLoadingText() }}

    -
    - } - - - @if (error && !loading) { - - } - - - @if (!loading && !error) { -
    - - - - - - - - - - - - - - @for (merchant of displayedMerchants; track merchant.id) { - - - - - - - - - - } - @empty { - - - - } - -
    -
    - Marchand - -
    -
    DescriptionContactConfigurationsContacts Tech -
    - Créé le - -
    -
    Actions
    -
    - @if (merchant.logo) { - Logo {{ merchant.name }} - } -
    - -
    -
    - {{ merchant.name }} - {{ merchant.adresse }} -
    -
    -
    - @if (merchant.description) { - {{ merchant.description }} - } @else { - Aucune description - } - -
    - - - {{ merchant.phone }} - - @if (merchant.technicalContacts && merchant.technicalContacts.length > 0) { - - - {{ merchant.technicalContacts[0].firstName }} {{ merchant.technicalContacts[0].lastName }} - - } -
    -
    -
    - @if (merchant.configs && merchant.configs.length > 0) { -
    - @for (config of merchant.configs.slice(0, 2); track config.id) { - - {{ getConfigTypeLabel(config.name) }} - - } - @if (merchant.configs.length > 2) { - - +{{ merchant.configs.length - 2 }} - - } -
    - - {{ merchant.configs.length }} configuration(s) - - } @else { - - Aucune config - - } -
    -
    - @if (merchant.technicalContacts && merchant.technicalContacts.length > 0) { -
    - - {{ merchant.technicalContacts.length }} contact(s) - - - {{ merchant.technicalContacts[0].email }} - -
    - } @else { - - Aucun contact - - } -
    - - {{ formatTimestamp(merchant.createdAt!) }} - - -
    - - - @if (showDeleteButton) { - - } -
    -
    -
    - -
    {{ getEmptyStateTitle() }}
    -

    {{ getEmptyStateDescription() }}

    - @if (showCreateButton && canCreateMerchants) { - - } -
    -
    -
    - - - @if (totalPages > 1) { -
    -
    - Affichage de {{ getStartIndex() }} à {{ getEndIndex() }} sur {{ totalItems }} marchands + + @if (loading) { +
    +
    + Chargement...
    - +

    {{ getLoadingText() }}

    } - - @if (displayedMerchants.length > 0) { -
    -
    -
    - - Total : {{ allMerchants.length }} marchands - -
    -
    - - Configurations : {{ getTotalConfigsCount() }} - -
    -
    - - Contacts : {{ getTotalContactsCount() }} - -
    + @if (error && !loading) { + } + + + @if (!loading && !error) { +
    + @if (displayedMerchants.length > 0) { + + + + + + + + + + + + + + + @for (merchant of displayedMerchants; track merchant.id) { + + + + + + + + + + } + @empty { + + + + } + +
    +
    + Marchand + +
    +
    DescriptionContactConfigurationsContacts Tech +
    + Créé le + +
    +
    Actions
    +
    + @if (merchant.logo) { + Logo {{ merchant.name }} + } +
    + +
    +
    + {{ merchant.name }} + {{ merchant.adresse }} +
    +
    +
    + @if (merchant.description) { + {{ merchant.description }} + } @else { + Aucune description + } + +
    + + + {{ merchant.phone }} + + @if (merchant.technicalContacts && merchant.technicalContacts.length > 0) { + + + {{ merchant.technicalContacts[0].firstName }} {{ merchant.technicalContacts[0].lastName }} + + } +
    +
    +
    + @if (merchant.configs && merchant.configs.length > 0) { +
    + @for (config of merchant.configs.slice(0, 2); track config.id) { + + {{ getConfigTypeLabel(config.name) }} + + } + @if (merchant.configs.length > 2) { + + +{{ merchant.configs.length - 2 }} + + } +
    + + {{ merchant.configs.length }} configuration(s) + + } @else { + + Aucune config + + } +
    +
    + @if (merchant.technicalContacts && merchant.technicalContacts.length > 0) { +
    + + {{ merchant.technicalContacts.length }} contact(s) + + + {{ merchant.technicalContacts[0].email }} + +
    + } @else { + + Aucun contact + + } +
    + + {{ formatTimestamp(merchant.createdAt!) }} + + +
    + + + @if (showDeleteButton) { + + } +
    +
    +
    + +
    {{ getEmptyStateTitle() }}
    +

    {{ getEmptyStateDescription() }}

    + @if (showCreateButton && canCreateMerchants) { + + } +
    +
    + + + @if (totalPages > 1) { +
    +
    + Affichage de {{ getStartIndex() }} + à {{ getEndIndex() }} + sur {{ totalItems }} marchands + @if (searchTerm || operatorFilter !== 'all') { + + Filtres actifs + + } +
    + +
    + } + + +
    +
    +
    + + Page : {{ currentPage }}/{{ totalPages }} + +
    +
    + + Résultats : {{ displayedMerchants.length }} sur {{ totalItems }} + +
    +
    + + Configurations : {{ getTotalConfigsCount() }} + +
    +
    + + Contacts : {{ getTotalContactsCount() }} + +
    +
    +
    + } @else { + +
    + +
    {{ getEmptyStateTitle() }}
    +

    {{ getEmptyStateDescription() }}

    + @if (showCreateButton && canCreateMerchants) { + + } +
    + } +
    + } }
    - \ No newline at end of file + diff --git a/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts b/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts index 43c2783..54308c0 100644 --- a/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts +++ b/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts @@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgIcon } from '@ng-icons/core'; import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; -import { Observable, Subject, map, of } from 'rxjs'; +import { Observable, Subject, of } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; import { @@ -12,6 +12,8 @@ import { Operator, MerchantUtils, UserRole, + PaginatedResponse, + SearchMerchantsParams, } from '@core/models/merchant-config.model'; import { MerchantConfigService } from '../merchant-config.service'; @@ -55,7 +57,6 @@ export class MerchantConfigsList implements OnInit, OnDestroy { // Données allMerchants: Merchant[] = []; - filteredMerchants: Merchant[] = []; displayedMerchants: Merchant[] = []; // États @@ -69,6 +70,7 @@ export class MerchantConfigsList implements OnInit, OnDestroy { // Pagination currentPage = 1; itemsPerPage = 10; + itemsPerPageOptions = [5, 10, 20, 50, 100]; // Options pour le sélecteur totalItems = 0; totalPages = 0; @@ -81,49 +83,9 @@ export class MerchantConfigsList implements OnInit, OnDestroy { // Permissions currentUserRole: any = null; - canViewAllMerchants = false; - currentMerchantConfigId: string | undefined; + isHubUser = false; - // ==================== CONVERSION IDS ==================== - - private convertIdToNumber(id: string): number { - const numId = Number(id); - if (isNaN(numId)) { - throw new Error(`ID invalide pour la conversion en number: ${id}`); - } - return numId; - } - - private convertIdToString(id: number): string { - return id.toString(); - } - - private convertMerchantToFrontend(merchant: any): Merchant { - return { - ...merchant, - id: merchant.id ? this.convertIdToString(merchant.id) : undefined, - configs: merchant.configs ? merchant.configs.map((config: any) => ({ - ...config, - id: config.id ? this.convertIdToString(config.id) : undefined, - merchantPartnerId: config.merchantPartnerId ? this.convertIdToString(config.merchantPartnerId) : undefined - })) : [], - technicalContacts: merchant.technicalContacts ? merchant.technicalContacts.map((contact: any) => ({ - ...contact, - id: contact.id ? this.convertIdToString(contact.id) : undefined, - merchantPartnerId: contact.merchantPartnerId ? this.convertIdToString(contact.merchantPartnerId) : undefined - })) : [], - users: merchant.users ? merchant.users.map((user: any) => ({ - ...user, - merchantPartnerId: user.merchantPartnerId ? this.convertIdToString(user.merchantPartnerId) : undefined - })) : [] - }; - } - - private convertMerchantsToFrontend(merchants: any[]): Merchant[] { - return merchants.map(merchant => this.convertMerchantToFrontend(merchant)); - } - - // Getters pour la logique conditionnelle + // Getters get showCreateButton(): boolean { return this.canCreateMerchants; } @@ -133,14 +95,11 @@ export class MerchantConfigsList implements OnInit, OnDestroy { } getColumnCount(): number { - return 8; + return 7; } ngOnInit() { this.initializeAvailableFilters(); - } - - ngAfterViewInit() { this.loadCurrentUserPermissions(); } @@ -155,10 +114,11 @@ export class MerchantConfigsList implements OnInit, OnDestroy { .subscribe({ next: (user) => { this.currentUserRole = this.extractUserRole(user); + this.isHubUser = this.checkIfHubUser(); - this.canViewAllMerchants = this.canViewAllMerchantsCheck(this.currentUserRole); - - this.loadMerchants(); + if (this.isHubUser) { + this.loadMerchants(); + } }, error: (error) => { console.error('Error loading current user permissions:', error); @@ -167,31 +127,20 @@ export class MerchantConfigsList implements OnInit, OnDestroy { }); } - /** - * Extraire le merchantPartnerId - */ - private extractMerchantConfigId(user: any): string { - if (user?.merchantConfigId) { - return user.merchantConfigId; - } - return ''; - } - - private extractUserRole(user: any): any { const userRoles = this.authService.getCurrentUserRoles(); return userRoles && userRoles.length > 0 ? userRoles[0] : null; } - private canViewAllMerchantsCheck(role: any): boolean { - if (!role) return false; + private checkIfHubUser(): boolean { + if (!this.currentUserRole) return false; - const canViewAllRoles = [ + const hubRoles = [ UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT ]; - return canViewAllRoles.includes(role); + return hubRoles.includes(this.currentUserRole); } private initializeAvailableFilters() { @@ -202,30 +151,48 @@ export class MerchantConfigsList implements OnInit, OnDestroy { } loadMerchants() { + if (!this.isHubUser) { + console.log('⚠️ User is not a Hub user, merchant list not displayed'); + return; + } + this.loading = true; this.error = ''; - let merchantsObservable: Observable; - - if (this.canViewAllMerchants) { - merchantsObservable = this.getAllMerchants(); - } else { - merchantsObservable = this.getMyMerchants(); - } - - merchantsObservable + this.merchantConfigService.getMerchants( + this.currentPage, + this.itemsPerPage, + this.buildSearchParams() + ) .pipe( takeUntil(this.destroy$), catchError(error => { console.error('Error loading merchants:', error); this.error = 'Erreur lors du chargement des marchands'; - return of([] as Merchant[]); + return of({ + items: [], + total: 0, + page: this.currentPage, + limit: this.itemsPerPage, + totalPages: 0 + } as PaginatedResponse); }) ) .subscribe({ - next: (merchants) => { - this.allMerchants = merchants || []; - this.applyFiltersAndPagination(); + next: (response) => { + console.log('📊 Pagination response:', { + page: response.page, + total: response.total, + totalPages: response.totalPages, + itemsCount: response.items?.length, + limit: response.limit + }); + + this.allMerchants = response.items || []; + this.displayedMerchants = response.items || []; + this.totalItems = response.total || 0; + this.totalPages = response.totalPages || 0; + this.loading = false; this.cdRef.detectChanges(); }, @@ -233,39 +200,90 @@ export class MerchantConfigsList implements OnInit, OnDestroy { this.error = 'Erreur lors du chargement des marchands'; this.loading = false; this.allMerchants = []; - this.filteredMerchants = []; this.displayedMerchants = []; + this.totalItems = 0; + this.totalPages = 0; this.cdRef.detectChanges(); } }); } - private getAllMerchants(): Observable { - return this.merchantConfigService.getMerchants(1, 1000).pipe( - map(response => { - return this.convertMerchantsToFrontend(response.items); - }), - catchError(error => { - console.error('Error getting all merchants:', error); - return of([]); - }) - ); + private buildSearchParams(): SearchMerchantsParams { + const params: SearchMerchantsParams = {}; + + if (this.searchTerm.trim()) { + params.query = this.searchTerm.trim(); + } + + return params; } - private getMyMerchants(): Observable { - const merchantConfigId = Number(this.currentMerchantConfigId); + // ==================== RECHERCHE ET FILTRES ==================== - return this.merchantConfigService.getMerchantUsers(merchantConfigId).pipe( - map(response => { - return this.convertMerchantsToFrontend(response); - }), - catchError(error => { - console.error('Error getting all merchants:', error); - return of([]); - }) - ); + onSearch() { + this.currentPage = 1; + this.loadMerchants(); } + onClearFilters() { + this.searchTerm = ''; + this.operatorFilter = 'all'; + this.currentPage = 1; + this.loadMerchants(); + } + + onFilterChange() { + this.currentPage = 1; + this.loadMerchants(); + } + + filterByOperator(operator: Operator | 'all') { + this.operatorFilter = operator; + this.currentPage = 1; + this.loadMerchants(); + } + + // ==================== GESTION DU NOMBRE D'ÉLÉMENTS PAR PAGE ==================== + + onItemsPerPageChange() { + console.log(`📊 Changing items per page to: ${this.itemsPerPage}`); + this.currentPage = 1; // Retour à la première page + this.loadMerchants(); + } + + // ==================== TRI ==================== + + sort(field: keyof Merchant) { + if (this.sortField === field) { + this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + this.sortField = field; + this.sortDirection = 'asc'; + } + this.currentPage = 1; + this.loadMerchants(); + } + + getSortIcon(field: string): string { + if (this.sortField !== field) return 'lucideArrowUpDown'; + return this.sortDirection === 'asc' ? 'lucideArrowUp' : 'lucideArrowDown'; + } + + // ==================== PAGINATION ==================== + + onPageChange(page: number) { + console.log(`📄 Page changed to: ${page}`); + this.currentPage = page; + this.loadMerchants(); + } + + getStartIndex(): number { + return this.totalItems > 0 ? (this.currentPage - 1) * this.itemsPerPage + 1 : 0; + } + + getEndIndex(): number { + return Math.min(this.currentPage * this.itemsPerPage, this.totalItems); + } // ==================== ACTIONS ==================== @@ -281,110 +299,10 @@ export class MerchantConfigsList implements OnInit, OnDestroy { this.deleteMerchantRequested.emit(merchant); } - // ==================== FILTRES ET RECHERCHE ==================== - - onSearch() { - this.currentPage = 1; - this.applyFiltersAndPagination(); - } - - onClearFilters() { - this.searchTerm = ''; - this.operatorFilter = 'all'; - this.currentPage = 1; - this.applyFiltersAndPagination(); - } - - filterByOperator(operator: Operator | 'all') { - this.operatorFilter = operator; - this.currentPage = 1; - this.applyFiltersAndPagination(); - } - - applyFiltersAndPagination() { - if (!this.allMerchants) { - this.allMerchants = []; - } - - // Appliquer les filtres - this.filteredMerchants = this.allMerchants.filter(merchant => { - const matchesSearch = !this.searchTerm || - merchant.name.toLowerCase().includes(this.searchTerm.toLowerCase()) || - merchant.adresse.toLowerCase().includes(this.searchTerm.toLowerCase()) || - merchant.phone.toLowerCase().includes(this.searchTerm.toLowerCase()) || - (merchant.description && merchant.description.toLowerCase().includes(this.searchTerm.toLowerCase())); - // Filtrer par opérateur basé sur les configurations - const matchesOperator = this.operatorFilter === 'all' || - (merchant.configs && merchant.configs.some(config => config.operatorId === this.operatorFilter)); - - return matchesSearch && matchesOperator; - }); - - // Appliquer le tri - this.filteredMerchants.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 (aValue instanceof Date && bValue instanceof Date) { - comparison = aValue.getTime() - bValue.getTime(); - } else if (typeof aValue === 'number' && typeof bValue === 'number') { - comparison = aValue - bValue; - } - - return this.sortDirection === 'asc' ? comparison : -comparison; - }); - - // Calculer la pagination - this.totalItems = this.filteredMerchants.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.displayedMerchants = this.filteredMerchants.slice(startIndex, endIndex); - } - - // ==================== TRI ==================== - - sort(field: keyof Merchant) { - if (this.sortField === field) { - this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc'; - } else { - this.sortField = field; - this.sortDirection = 'asc'; - } - this.applyFiltersAndPagination(); - } - - getSortIcon(field: string): string { - if (this.sortField !== field) return 'lucideArrowUpDown'; - return this.sortDirection === 'asc' ? 'lucideArrowUp' : 'lucideArrowDown'; - } - - // ==================== PAGINATION ==================== - - onPageChange(page: number) { - 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); - } - // ==================== MÉTHODES STATISTIQUES ==================== getTotalMerchantsCount(): number { - return this.allMerchants.length; + return this.totalItems; } getTotalConfigsCount(): number { @@ -417,23 +335,20 @@ export class MerchantConfigsList implements OnInit, OnDestroy { // ==================== MÉTHODES POUR LE TEMPLATE ==================== refreshData() { + this.currentPage = 1; this.loadMerchants(); } getCardTitle(): string { - return this.canViewAllMerchants - ? 'Tous les Marchands' - : 'Mes Marchands'; + return 'Tous les Marchands'; } getHelperText(): string { - return this.canViewAllMerchants - ? 'Vue administrative - Gestion de tous les marchands' - : 'Vos marchands partenaires'; + return 'Vue administrative - Gestion de tous les marchands'; } getHelperIcon(): string { - return this.canViewAllMerchants ? 'lucideShield' : 'lucideStore'; + return 'lucideShield'; } getLoadingText(): string { @@ -451,4 +366,8 @@ export class MerchantConfigsList implements OnInit, OnDestroy { getEmptyStateButtonText(): string { return 'Créer le premier marchand'; } + + shouldDisplayMerchantList(): boolean { + return this.isHubUser; + } } \ No newline at end of file diff --git a/src/app/modules/merchant-config/merchant-config.html b/src/app/modules/merchant-config/merchant-config.html index aa03024..acb70e9 100644 --- a/src/app/modules/merchant-config/merchant-config.html +++ b/src/app/modules/merchant-config/merchant-config.html @@ -62,15 +62,11 @@ [destroyOnHide]="false" class="nav nav-tabs nav-justified nav-bordered nav-bordered-primary mb-3" > -
  • +
  • - @if (isMerchantUser) { - Utilisateurs - } @else { - Marchands - } + Marchands @@ -86,8 +82,8 @@
  • -
  • - +
  • + @if (isMerchantUser) { @@ -98,11 +94,11 @@ - @if (showProfileTab) { + @if (showMerchantProfileTab) { @if (isMerchantUser && userMerchantId) { } @else if (!isMerchantUser && selectedMerchantId) { { @@ -54,46 +63,183 @@ export class MerchantConfigService { } getMerchants(page: number = 1, limit: number = 10, params?: SearchMerchantsParams): Observable> { - let httpParams = new HttpParams() - .set('page', page.toString()) - .set('limit', limit.toString()); + // Vérifier si le cache est valide + const paramsChanged = !this.areParamsEqual(params, this.cacheParams); + + if (paramsChanged || this.merchantsCache.length === 0) { + // Nouvelle requête nécessaire + return this.fetchAllMerchants(params).pipe( + map(allMerchants => { + // Mettre en cache + this.merchantsCache = allMerchants; + this.cacheParams = params; + + // Appliquer pagination + return this.applyPagination(allMerchants, page, limit); + }) + ); + } else { + // Vérifier si le cache contient assez d'éléments pour la page demandée + const neededItems = page * limit; + + if (neededItems <= this.merchantsCache.length) { + // Cache suffisant + return of(this.applyPagination(this.merchantsCache, page, limit)); + } else { + // Besoin de plus d'éléments + const additionalNeeded = neededItems - this.merchantsCache.length; + const newTake = this.calculateOptimalTake(additionalNeeded, page, limit); + + console.log(`🔄 Cache insufficient (${this.merchantsCache.length}), fetching ${newTake} more items`); + + return this.fetchAdditionalMerchants(newTake, params).pipe( + map(additionalMerchants => { + // Fusionner avec le cache (éviter les doublons) + const merged = this.mergeMerchants(this.merchantsCache, additionalMerchants); + this.merchantsCache = merged; + + return this.applyPagination(merged, page, limit); + }) + ); + } + } + } + // Calculer le take optimal en fonction du total + private calculateOptimalTake(totalCount: number, page: number, limit: number): number { + // Calculer combien d'éléments nous avons besoin pour la pagination + const neededItems = page * limit; + + // Ajouter un buffer pour éviter les appels fréquents + const buffer = Math.max(limit * 2, 100); + + // Calculer le take nécessaire + let optimalTake = neededItems + buffer; + + // Si le total est connu, adapter le take + if (totalCount > 0) { + // Prendre soit ce dont on a besoin, soit le total (le plus petit) + optimalTake = Math.min(optimalTake, totalCount); + } + + // Arrondir aux paliers optimaux + return this.roundToOptimalValue(optimalTake); + } + + // Arrondir à des valeurs optimales (100, 500, 1000, etc.) + private roundToOptimalValue(value: number): number { + if (value <= 100) return 100; + if (value <= 500) return 500; + if (value <= 1000) return 1000; + if (value <= 2000) return 2000; + if (value <= 5000) return 5000; + if (value <= 10000) return 10000; + + // Pour les très grands nombres, arrondir au multiple de 10000 supérieur + return Math.ceil(value / 10000) * 10000; + } + + private fetchAllMerchants(params?: SearchMerchantsParams): Observable { + // Commencer avec un take raisonnable + const initialTake = 500; + + return this.fetchMerchantsWithParams(initialTake, params).pipe( + switchMap(initialBatch => { + // Si nous avons récupéré moins que demandé, c'est probablement tout + if (initialBatch.length < initialTake) { + console.log(`✅ Retrieved all ${initialBatch.length} merchants`); + return of(initialBatch); + } + + // Sinon, peut-être qu'il y a plus, essayer un take plus grand + console.log(`⚠️ Initial batch size (${initialBatch.length}) equals take, might be more`); + + const largerTake = 2000; + return this.fetchMerchantsWithParams(largerTake, params).pipe( + map(largerBatch => { + console.log(`✅ Retrieved ${largerBatch.length} merchants with larger take`); + return largerBatch; + }) + ); + }) + ); + } + + private fetchMerchantsWithParams(take: number, params?: SearchMerchantsParams): Observable { + let httpParams = new HttpParams().set('take', take.toString()); + if (params?.query) { httpParams = httpParams.set('query', params.query.trim()); } - console.log(`📥 Loading merchants page ${page}, limit ${limit}`, params); - + console.log(`📥 Fetching ${take} merchants`); + return this.http.get(this.baseApiUrl, { params: httpParams }).pipe( timeout(this.REQUEST_TIMEOUT), - retry(this.MAX_RETRIES), - map(apiMerchants => { - const total = apiMerchants.length; - const totalPages = Math.ceil(total / limit); - - const startIndex = (page - 1) * limit; - const endIndex = startIndex + limit; - const paginatedItems = apiMerchants.slice(startIndex, endIndex); - - const response: PaginatedResponse = { - items: paginatedItems.map(apiMerchant => - this.dataAdapter.convertApiMerchantToFrontend(apiMerchant) - ), - total: total, - page: page, - limit: limit, - totalPages: totalPages - }; - - console.log(`✅ Loaded ${response.items.length} merchants`); - return response; - }), - catchError(error => this.handleError('getMerchants', error)) + map(apiMerchants => + apiMerchants.map(merchant => + this.dataAdapter.convertApiMerchantToFrontend(merchant) + ) + ) ); } + private fetchAdditionalMerchants(take: number, params?: SearchMerchantsParams): Observable { + // Prendre à partir de la fin du cache + const skip = this.merchantsCache.length; + let httpParams = new HttpParams() + .set('take', take.toString()) + .set('skip', skip.toString()); // Si votre API supporte skip + + if (params?.query) { + httpParams = httpParams.set('query', params.query.trim()); + } + + console.log(`📥 Fetching additional ${take} merchants (skip: ${skip})`); + + return this.http.get(this.baseApiUrl, { + params: httpParams + }).pipe( + timeout(this.REQUEST_TIMEOUT), + map(apiMerchants => + apiMerchants.map(merchant => + this.dataAdapter.convertApiMerchantToFrontend(merchant) + ) + ) + ); + } + + private mergeMerchants(existing: Merchant[], newOnes: Merchant[]): Merchant[] { + const existingIds = new Set(existing.map(m => m.id)); + const uniqueNewOnes = newOnes.filter(m => !existingIds.has(m.id)); + return [...existing, ...uniqueNewOnes]; + } + + private applyPagination(merchants: Merchant[], page: number, limit: number): PaginatedResponse { + const total = merchants.length; + const totalPages = Math.ceil(total / limit); + + const startIndex = (page - 1) * limit; + const endIndex = Math.min(startIndex + limit, total); + const paginatedItems = merchants.slice(startIndex, endIndex); + + return { + items: paginatedItems, + total: total, + page: page, + limit: limit, + totalPages: totalPages + }; + } + + private areParamsEqual(a?: SearchMerchantsParams, b?: SearchMerchantsParams): boolean { + if (!a && !b) return true; + if (!a || !b) return false; + return a.query === b.query; + } + getAllMerchants(params?: SearchMerchantsParams): Observable { let httpParams = new HttpParams(); @@ -176,20 +322,149 @@ export class MerchantConfigService { ); } - getMerchantUsers(merchantId: number): Observable { - //const merchantId = this.convertIdToNumber(merchantId); - - return this.http.get(`${this.baseApiUrl}/${merchantId}/users`).pipe( - timeout(this.REQUEST_TIMEOUT), - map(apiMerchant => { - return (apiMerchant.users || []).map(user => - this.dataAdapter.convertApiUserToFrontend(user) + getMerchantUsers( + merchantId: number, + page: number = 1, + limit: number = 10, + params?: SearchUsersParams + ): Observable> { + + return this.tryMerchantConfigApi(merchantId, params).pipe( + switchMap(merchantConfigUsers => { + // Si MerchantConfig retourne des données, les utiliser + if (merchantConfigUsers && merchantConfigUsers.length > 0) { + console.log(`✅ Using ${merchantConfigUsers.length} users from MerchantConfig API`); + return of(this.paginateUsers(merchantConfigUsers, page, limit)); + } + + // Sinon, fallback à IAM API + console.log('🔄 MerchantConfig API returned no data, falling back to IAM API'); + return this.getUsersFromIamApi(merchantId).pipe( + map(iamUsers => this.paginateUsers(iamUsers, page, limit)) ); }), - catchError(error => this.handleError('getMerchantUsers', error, { merchantId })) + catchError(error => { + console.warn('⚠️ MerchantConfig API failed, using IAM API:', error); + return this.getUsersFromIamApi(merchantId).pipe( + map(iamUsers => this.paginateUsers(iamUsers, page, limit)) + ); + }) ); } + private tryMerchantConfigApi(merchantId: number, params?: SearchUsersParams): Observable { + const httpParams = this.buildMerchantConfigParams(params); + + return this.http.get( + `${this.baseApiUrl}/${merchantId}/users`, + { params: httpParams } + ).pipe( + timeout(this.REQUEST_TIMEOUT), + map(configUsers => { + if (!configUsers) return []; + + const items = this.extractItemsFromResponse(configUsers); + return items.map(item => this.convertToMerchantUser(item, merchantId)); + }), + catchError(error => { + // Retourner un tableau vide pour déclencher le fallback + return of([]); + }) + ); + } + + private getUsersFromIamApi(merchantId: number ): Observable { + return this.merchantUsersService.getMyMerchantUsers().pipe( + map(iamUsers => { + + if (!iamUsers) return []; + + const items = this.extractItemsFromResponse(iamUsers); + return items.map(item => this.convertToMerchantUser(item, merchantId)); + + }), + catchError(error => { + console.error('❌ IAM API failed:', error); + return of([]); + }) + ); + } + + private paginateUsers(users: MerchantUser[], page: number, limit: number): PaginatedResponse { + const total = users.length; + const startIndex = (page - 1) * limit; + const endIndex = startIndex + limit; + const paginatedItems = users.slice(startIndex, endIndex); + + return { + items: paginatedItems, + total: total, + page: page, + limit: limit, + totalPages: Math.ceil(total / limit) + }; + } + + private buildMerchantConfigParams(params?: SearchUsersParams): HttpParams { + let httpParams = new HttpParams(); + + if (params?.searchTerm?.trim()) { + httpParams = httpParams.set('search', params.searchTerm.trim()); + } + + if (params?.role) { + httpParams = httpParams.set('role', params.role); + } + + return httpParams; + } + + private extractItemsFromResponse(response: any): any[] { + if (Array.isArray(response)) { + return response; + } + + if (response && typeof response === 'object') { + if (response.items && Array.isArray(response.items)) { + return response.items; + } + if (response.users && Array.isArray(response.users)) { + return response.users; + } + if (response.data && Array.isArray(response.data)) { + return response.data; + } + + // Chercher le premier tableau + const arrayKeys = Object.keys(response).filter(key => Array.isArray(response[key])); + if (arrayKeys.length > 0) { + return response[arrayKeys[0]]; + } + } + + return []; + } + + private convertToMerchantUser(item: any, merchantId: number): MerchantUser { + try { + return this.dataAdapter.convertApiUserToFrontend(item); + } catch (error) { + console.warn('Adapter conversion failed, using manual conversion:', error); + return { + userId: item.userId || item.id || '', + firstName: item.firstName || item.firstname || '', + lastName: item.lastName || item.lastname || '', + email: item.email || '', + role: item.role || '', + merchantPartnerId: item.merchantPartnerId || merchantId.toString(), + enabled: item.enabled !== undefined ? item.enabled : true, + emailVerified: item.emailVerified !== undefined ? item.emailVerified : false, + createdAt: item.createdAt || item.createdDate || new Date().toISOString(), + updatedAt: item.updatedAt || item.updatedDate || new Date().toISOString() + }; + } + } + getAllMerchantUsers(page: number = 1, limit: number = 10, params?: SearchMerchantsParams): Observable> { let httpParams = new HttpParams(); diff --git a/src/app/modules/merchant-config/merchant-config.ts b/src/app/modules/merchant-config/merchant-config.ts index 19dd552..b5b1e19 100644 --- a/src/app/modules/merchant-config/merchant-config.ts +++ b/src/app/modules/merchant-config/merchant-config.ts @@ -64,9 +64,10 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { badge: any = { icon: 'lucideSettings', text: 'Merchant Management' }; // État de l'interface - activeTab: 'list' | 'profile' = 'list'; + activeTab: 'list' | 'merchant-profile' = 'list'; selectedMerchantId: number | null = null; selectedConfigId: number | null = null; + selectedUserId: string | null = null; // Gestion des permissions currentUserRole: UserRole | null = null; @@ -145,15 +146,36 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { merchantUsersError = ''; currentMerchantPartnerId: string = ''; + isInitializing = true; + ngOnInit() { this.loadCurrentUserPermissions(); this.initializeUserType(); this.initializeMerchantPartnerContext(); - - // Déterminer l'onglet initial en fonction du type d'utilisateur - this.setInitialTab(); } + ngAfterViewInit() { + setTimeout(() => { + this.setInitialTab(); + }); + } + + private setInitialTab(): void { + this.isInitializing = true; + + if (this.isMerchantUser) { + // Pour un utilisateur marchand + this.activeTab = 'merchant-profile'; + this.loadUserMerchant(); + } else { + // Pour un utilisateur Hub + this.activeTab = 'list'; + this.isInitializing = false; + this.cdRef.detectChanges(); + } + } + + /** * Initialise le type d'utilisateur */ @@ -181,44 +203,21 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { console.log(`Is Hub User: ${this.isHubUser}`); } - /** - * Définit l'onglet initial en fonction du type d'utilisateur - */ - private setInitialTab(): void { - if (this.isMerchantUser) { - // Pour un utilisateur marchand, charger directement son marchand ET les utilisateurs - this.activeTab = 'profile'; // On change pour afficher d'abord la liste des utilisateurs - this.loadUserMerchantAndUsers(); - } else { - // Pour un utilisateur Hub, afficher la liste des marchands - this.activeTab = 'list'; - } - } - - /** - * Charge le marchand ET les utilisateurs pour un merchant user - */ - private loadUserMerchantAndUsers(): void { - if (!this.isMerchantUser || !this.currentMerchantConfigId) return; - - // Charger le marchand de l'utilisateur - this.loadUserMerchant(); - - // Charger les utilisateurs du marchand - this.loadMerchantUsers(); - } - - /** + /** * Charge le marchand de l'utilisateur (pour les merchant users) */ + private loadUserMerchant(): void { - if (!this.isMerchantUser || !this.currentMerchantConfigId) return; - + if (!this.isMerchantUser || !this.currentMerchantConfigId) { + this.isInitializing = false; + this.cdRef.detectChanges(); + return; + } + this.loadingUserMerchant = true; - - const merchantConfigId = Number(this.currentMerchantConfigId); - - this.merchantConfigService.getMerchantById(merchantConfigId) + const merchantPartnerId = Number(this.currentMerchantConfigId); + + this.merchantConfigService.getMerchantById(merchantPartnerId) .pipe(takeUntil(this.destroy$)) .subscribe({ next: (merchant) => { @@ -226,68 +225,28 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { const frontendMerchant = this.convertMerchantToFrontend(merchant); this.userMerchantId = frontendMerchant.id!; this.userMerchant = frontendMerchant; - - // Afficher automatiquement le profil du marchand - this.showTab('profile', merchantConfigId); - + + // Vérifier que nous sommes toujours sur l'onglet profil + if (this.activeTab === 'merchant-profile') { + this.showTab('merchant-profile', merchantPartnerId); + } } else { console.warn('No merchant found for current user'); + // Si aucun marchand trouvé, revenir à la liste + this.activeTab = 'list'; } + this.loadingUserMerchant = false; + this.isInitializing = false; + this.cdRef.detectChanges(); }, error: (error) => { console.error('Error loading user merchant:', error); this.loadingUserMerchant = false; - } - }); - } - - /** - * Charge les utilisateurs du marchand (pour les merchant users) - */ - private loadMerchantUsers(): void { - if (!this.isMerchantUser || !this.currentMerchantConfigId) return; - - this.loadingMerchantUsers = true; - this.merchantUsersError = ''; - - this.merchantSyncService.getUsersByMerchant( - this.currentMerchantConfigId - ) - .pipe( - takeUntil(this.destroy$), - catchError(error => { - console.error('Error loading merchant users:', error); - this.merchantUsersError = 'Erreur lors du chargement des utilisateurs'; - this.loadingMerchantUsers = false; - return of({ users: [] }); - }) - ) - .subscribe({ - next: (response: any) => { - // Transformer les données des utilisateurs - this.merchantUsers = response.users.map((userData: any) => { - const keycloakUser = userData.keycloakUser || {}; - const merchantConfigUser = userData.merchantConfigUser || {}; - - return { - id: keycloakUser.id || merchantConfigUser.userId, - username: keycloakUser.username, - email: keycloakUser.email, - firstName: keycloakUser.firstName, - lastName: keycloakUser.lastName, - role: keycloakUser.role || merchantConfigUser.role, - enabled: keycloakUser.enabled, - emailVerified: keycloakUser.emailVerified, - merchantPartnerId: this.currentMerchantPartnerId, - merchantConfigId: this.currentMerchantConfigId, - createdDate: keycloakUser.createdDate || merchantConfigUser.createdDate, - lastModifiedDate: keycloakUser.lastModifiedDate || merchantConfigUser.lastModifiedDate - } as unknown as User; - }); - - console.log(`Loaded ${this.merchantUsers.length} users for merchant`); - this.loadingMerchantUsers = false; + this.isInitializing = false; + // Revenir à la liste en cas d'erreur + this.activeTab = 'list'; + this.cdRef.detectChanges(); } }); } @@ -441,7 +400,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { } private extractMerchantConfigId(user: any): string { - return user?.merchantConfigId || ''; + return user?.merchantPartnerId || ''; } // ==================== PERMISSIONS SPÉCIFIQUES MARCHAND ==================== @@ -503,35 +462,50 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { // ==================== GESTION DES ONGLETS ==================== - showTab(tab: 'list' | 'profile', merchantId?: number): void { - console.log(`Switching to tab: ${tab}`, merchantId ? `for merchant ${merchantId}` : ''); + // Méthode pour changer d'onglet + showTab(tab: 'list' | 'merchant-profile', merchantId?: number): void { + // Ne pas permettre de changer d'onglet pendant l'initialisation + if (this.isInitializing && tab !== this.activeTab) { + return; + } + + console.log(`🔄 Switching to tab: ${tab}`, merchantId ? `with id: ${merchantId}` : ''); - // Pour tous les utilisateurs, permettre de basculer entre les onglets this.activeTab = tab; - if (tab === 'profile') { - // Déterminer l'ID du marchand à afficher + if (tab === 'merchant-profile' && merchantId) { if (this.isMerchantUser) { - // Pour les merchant users, toujours utiliser leur propre marchand - this.selectedMerchantId = this.userMerchantId || null; + this.userMerchantId = merchantId; } else { - // Pour les Hub users, utiliser l'ID fourni ou null - this.selectedMerchantId = merchantId || null; - } - - // Charger le profil si pas déjà chargé et si un ID est disponible - if (this.selectedMerchantId && !this.merchantProfiles[this.selectedMerchantId]) { - this.loadMerchantProfile(this.selectedMerchantId); - } - } else { - // Pour l'onglet 'list', pas besoin d'ID de marchand sélectionné - this.selectedMerchantId = null; - - // Si c'est un merchant user et qu'on va sur l'onglet liste, recharger les utilisateurs - if (this.isMerchantUser) { - this.refreshMerchantUsers(); + this.selectedMerchantId = merchantId; } } + + this.cdRef.detectChanges(); + } + + get showMerchantProfileTab(): boolean { + // Ne pas montrer l'onglet pendant l'initialisation pour un merchant user + if (this.isInitializing && this.isMerchantUser) { + return false; + } + + if (this.isMerchantUser) { + return !!this.userMerchantId; + } else { + return !!this.selectedMerchantId; + } + } + + // Méthode spécifique pour afficher le profil marchand + showMerchantProfile(merchantId: number): void { + console.log(`🏪 Showing merchant profile: ${merchantId}`); + if (this.isMerchantUser) { + this.userMerchantId = merchantId; + } else { + this.selectedMerchantId = merchantId; + } + this.activeTab = 'merchant-profile'; } @@ -585,7 +559,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { // ==================== ÉVÉNEMENTS DES COMPOSANTS ENFANTS ==================== onMerchantSelected(merchantId: number): void { - this.showTab('profile', merchantId); + // Pour un Hub user, sélectionner un marchand = voir les détails + this.showMerchantProfile(merchantId); } onEditMerchantRequested(merchant: Merchant): void { @@ -949,15 +924,6 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { } } - /** - * Rafraîchit la liste des utilisateurs du marchand - */ - refreshMerchantUsers(): void { - if (this.isMerchantUser) { - this.loadMerchantUsers(); - } - } - // ==================== GESTION DES ERREURS ==================== private getCreateErrorMessage(error: any): string { @@ -1026,56 +992,33 @@ export class MerchantConfigManagement implements OnInit, OnDestroy { // Propriété pour le message de succès successMessage: string = ''; - // ==================== GETTERS TEMPLATE ==================== + // ==================== GETTERS TEMPLATE ==================== - get showListTab(): boolean { - // Pour les merchant users, toujours montrer l'onglet liste (pour les utilisateurs) - // Pour les Hub users, montrer la liste si autorisé + // ==================== GETTERS POUR LES ONGLETS ==================== + + get shouldShowMerchantProfileContent(): boolean { if (this.isMerchantUser) { - return true; + return !!this.userMerchantId; + } else { + return !!this.selectedMerchantId; } - return this.canViewMerchantList; } - get showProfileTab(): boolean { - // Pour les merchant users, toujours montrer l'onglet profile (pour leur marchand) - // Pour les Hub users, montrer l'onglet profile si un marchand est sélectionné + getMerchantProfileId(): number | null{ if (this.isMerchantUser) { - return !!this.userMerchantId; // Montrer seulement si le marchand est chargé + return this.userMerchantId; + } else { + return this.selectedMerchantId; } - return !!this.selectedMerchantId; } - get pageTitleForUser(): string { + get activeMerchantProfileId(): number | null { + // Retourner l'ID du marchand à afficher dans l'onglet profil if (this.isMerchantUser) { - return this.activeTab === 'list' ? 'Utilisateurs du Marchand' : 'Mon Marchand'; + return this.userMerchantId; + } else { + return this.selectedMerchantId; } - return this.pageTitle; - } - - get pageSubtitleForUser(): string { - if (this.isMerchantUser) { - return this.activeTab === 'list' - ? 'Gérez les utilisateurs de votre marchand' - : 'Gérez la configuration technique de votre marchand'; - } - return this.pageSubtitle; - } - - get badgeForUser(): any { - if (this.isMerchantUser) { - return this.activeTab === 'list' - ? { icon: 'lucideUsers', text: 'Users' } - : { icon: 'lucideStore', text: 'My Merchant' }; - } - return this.badge; - } - - /** - * Détermine ce qui doit être affiché dans la liste - */ - get shouldDisplayMerchantUsers(): boolean { - return this.isMerchantUser && this.activeTab === 'list'; } get shouldDisplayMerchantList(): boolean {