dcb-backoffice/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts

440 lines
13 KiB
TypeScript

import { Component, inject, OnInit, Output, EventEmitter, ChangeDetectorRef, Input, OnDestroy } from '@angular/core';
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 { catchError, takeUntil } from 'rxjs/operators';
import {
Merchant,
ConfigType,
Operator,
MerchantUtils,
} from '@core/models/merchant-config.model';
import { MerchantConfigService } from '../merchant-config.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';
@Component({
selector: 'app-merchant-config-list',
standalone: true,
imports: [
CommonModule,
FormsModule,
NgIcon,
UiCard,
NgbPaginationModule
],
templateUrl: './merchant-config-list.html',
})
export class MerchantConfigsList implements OnInit, OnDestroy {
private authService = inject(AuthService);
private merchantConfigService = inject(MerchantConfigService);
protected roleService = inject(RoleManagementService);
private cdRef = inject(ChangeDetectorRef);
private destroy$ = new Subject<void>();
// Configuration
readonly ConfigType = ConfigType;
readonly Operator = Operator;
readonly MerchantUtils = MerchantUtils;
// Inputs
@Input() canCreateMerchants: boolean = false;
@Input() canDeleteMerchants: boolean = false;
// Outputs
@Output() merchantSelected = new EventEmitter<string>();
@Output() openCreateMerchantModal = new EventEmitter<void>();
@Output() editMerchantRequested = new EventEmitter<Merchant>();
@Output() deleteMerchantRequested = new EventEmitter<Merchant>();
@Output() activateMerchantRequested = new EventEmitter<Merchant>();
@Output() deactivateMerchantRequested = new EventEmitter<Merchant>();
// Données
allMerchants: Merchant[] = [];
filteredMerchants: Merchant[] = [];
displayedMerchants: Merchant[] = [];
// États
loading = false;
error = '';
// Recherche et filtres
searchTerm = '';
operatorFilter: Operator | 'all' = 'all';
// Pagination
currentPage = 1;
itemsPerPage = 10;
totalItems = 0;
totalPages = 0;
// Tri
sortField: keyof Merchant = 'name';
sortDirection: 'asc' | 'desc' = 'asc';
// Filtres disponibles
availableOperators: { value: Operator | 'all'; label: string }[] = [];
// Permissions
currentUserRole: any = null;
canViewAllMerchants = 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
get showCreateButton(): boolean {
return this.canCreateMerchants;
}
get showDeleteButton(): boolean {
return this.canDeleteMerchants;
}
getColumnCount(): number {
return 8; // Nombre de colonnes dans le tableau
}
ngOnInit() {
this.initializeAvailableFilters();
}
ngAfterViewInit() {
this.loadCurrentUserPermissions();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
private loadCurrentUserPermissions() {
this.authService.getUserProfile()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (user) => {
this.currentUserRole = this.extractUserRole(user);
this.canViewAllMerchants = this.canViewAllMerchantsCheck(this.currentUserRole);
this.loadMerchants();
},
error: (error) => {
console.error('Error loading current user permissions:', error);
this.loadMerchants();
}
});
}
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;
const canViewAllRoles = [
'DCB_ADMIN',
'DCB_SUPPORT',
'DCB_PARTNER_ADMIN'
];
return canViewAllRoles.includes(role);
}
private initializeAvailableFilters() {
this.availableOperators = [
{ value: 'all', label: 'Tous les opérateurs' },
{ value: Operator.ORANGE_OSN, label: 'Orange' }
];
}
loadMerchants() {
this.loading = true;
this.error = '';
let merchantsObservable: Observable<Merchant[]>;
if (this.canViewAllMerchants) {
merchantsObservable = this.getAllMerchants();
} else {
merchantsObservable = this.getMyMerchants();
}
merchantsObservable
.pipe(
takeUntil(this.destroy$),
catchError(error => {
console.error('Error loading merchants:', error);
this.error = 'Erreur lors du chargement des marchands';
return of([] as Merchant[]);
})
)
.subscribe({
next: (merchants) => {
this.allMerchants = merchants || [];
this.applyFiltersAndPagination();
this.loading = false;
this.cdRef.detectChanges();
},
error: () => {
this.error = 'Erreur lors du chargement des marchands';
this.loading = false;
this.allMerchants = [];
this.filteredMerchants = [];
this.displayedMerchants = [];
this.cdRef.detectChanges();
}
});
}
private getAllMerchants(): Observable<Merchant[]> {
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 getMyMerchants(): Observable<Merchant[]> {
return this.getAllMerchants();
}
// ==================== ACTIONS ====================
viewMerchantProfile(merchant: Merchant) {
this.merchantSelected.emit(merchant.id!);
}
editMerchant(merchant: Merchant) {
this.editMerchantRequested.emit(merchant);
}
deleteMerchant(merchant: Merchant) {
this.deleteMerchantRequested.emit(merchant);
}
activateMerchant(merchant: Merchant) {
this.activateMerchantRequested.emit(merchant);
}
deactivateMerchant(merchant: Merchant) {
this.deactivateMerchantRequested.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;
}
getTotalConfigsCount(): number {
return this.allMerchants.reduce((total, merchant) =>
total + (merchant.configs ? merchant.configs.length : 0), 0);
}
getTotalContactsCount(): number {
return this.allMerchants.reduce((total, merchant) =>
total + (merchant.technicalContacts ? merchant.technicalContacts.length : 0), 0);
}
// ==================== MÉTHODES D'AFFICHAGE ====================
getConfigTypeLabel(configName: ConfigType | string): string {
return MerchantUtils.getConfigTypeName(configName);
}
formatTimestamp(timestamp: string): 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'
});
}
// ==================== MÉTHODES POUR LE TEMPLATE ====================
refreshData() {
this.loadMerchants();
}
getCardTitle(): string {
return this.canViewAllMerchants
? 'Tous les Marchands'
: 'Mes Marchands';
}
getHelperText(): string {
return this.canViewAllMerchants
? 'Vue administrative - Gestion de tous les marchands'
: 'Vos marchands partenaires';
}
getHelperIcon(): string {
return this.canViewAllMerchants ? 'lucideShield' : 'lucideStore';
}
getLoadingText(): string {
return 'Chargement des marchands...';
}
getEmptyStateTitle(): string {
return 'Aucun marchand trouvé';
}
getEmptyStateDescription(): string {
return 'Aucun marchand ne correspond à vos critères de recherche.';
}
getEmptyStateButtonText(): string {
return 'Créer le premier marchand';
}
}