440 lines
13 KiB
TypeScript
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';
|
|
}
|
|
} |