1068 lines
41 KiB
HTML
1068 lines
41 KiB
HTML
<div class="container-fluid">
|
|
<app-page-title
|
|
[title]="pageTitle"
|
|
[subTitle]="pageSubtitle"
|
|
[badge]="badge"
|
|
/>
|
|
|
|
<!-- Indicateur de permissions -->
|
|
@if (currentUserRole) {
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="alert alert-info py-2">
|
|
<div class="d-flex align-items-center">
|
|
<ng-icon name="lucideInfo" class="me-2"></ng-icon>
|
|
<div class="flex-grow-1">
|
|
<small>
|
|
<strong>Rôle actuel :</strong>
|
|
<span class="badge" [ngClass]="getRoleBadgeClass(currentUserRole)">
|
|
{{ getRoleLabel(currentUserRole) }}
|
|
</span>
|
|
@if (!canCreateMerchants) {
|
|
<span class="text-warning ms-2">
|
|
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
|
Permissions limitées
|
|
</span>
|
|
}
|
|
@if (selectedMerchantConfigId) {
|
|
<span class="ms-2">
|
|
<strong>Marchand :</strong> {{ selectedMerchantConfigId }}
|
|
</span>
|
|
}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Message de succès -->
|
|
@if (successMessage) {
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="alert alert-success alert-dismissible fade show">
|
|
<div class="d-flex align-items-center">
|
|
<ng-icon name="lucideCheckCircle" class="me-2"></ng-icon>
|
|
<div>{{ successMessage }}</div>
|
|
<button type="button" class="btn-close ms-auto" (click)="successMessage = ''"></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Navigation par onglets -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<ul
|
|
ngbNav
|
|
#configsNav="ngbNav"
|
|
[activeId]="activeTab"
|
|
[destroyOnHide]="false"
|
|
class="nav nav-tabs nav-justified nav-bordered nav-bordered-primary mb-3"
|
|
>
|
|
<li [ngbNavItem]="'list'" [hidden]="isMerchantUser">
|
|
<a ngbNavLink (click)="showTab('list')">
|
|
<ng-icon name="lucideList" class="fs-lg me-md-1 d-inline-flex align-middle" />
|
|
<span class="d-none d-md-inline-block align-middle">
|
|
Marchands
|
|
</span>
|
|
</a>
|
|
<ng-template ngbNavContent>
|
|
<app-merchant-config-list
|
|
#merchantConfigsList
|
|
[canCreateMerchants]="canCreateMerchants"
|
|
[canDeleteMerchants]="canDeleteMerchants"
|
|
(merchantSelected)="onMerchantSelected($event)"
|
|
(openCreateMerchantModal)="openCreateMerchantModal()"
|
|
(editMerchantRequested)="onEditMerchantRequested($event)"
|
|
(deleteMerchantRequested)="onDeleteMerchantRequested($event)"
|
|
/>
|
|
</ng-template>
|
|
</li>
|
|
|
|
<li [ngbNavItem]="'merchant-profile'" [hidden]="!showMerchantProfileTab">
|
|
<a ngbNavLink (click)="showTab('merchant-profile')">
|
|
<ng-icon name="lucideSettings" class="fs-lg me-md-1 d-inline-flex align-middle" />
|
|
<span class="d-none d-md-inline-block align-middle">
|
|
@if (isMerchantUser) {
|
|
Mon Marchand
|
|
} @else {
|
|
Détails Marchand
|
|
}
|
|
</span>
|
|
</a>
|
|
<ng-template ngbNavContent>
|
|
@if (showMerchantProfileTab) {
|
|
<!-- Pour les merchant users, utiliser userMerchantId, pour les autres selectedMerchantId -->
|
|
@if (isMerchantUser && userMerchantId) {
|
|
<app-merchant-config-view
|
|
[merchantId]="userMerchantId!"
|
|
(openCreateMerchantModal)="openCreateMerchantModal()"
|
|
(editMerchantRequested)="onEditMerchantRequested($event)"
|
|
(editConfigRequested)="onEditConfigRequested($event)"
|
|
(back)="backToList()"
|
|
/>
|
|
} @else if (!isMerchantUser && selectedMerchantId) {
|
|
<app-merchant-config-view
|
|
[merchantId]="selectedMerchantId!"
|
|
(openCreateMerchantModal)="openCreateMerchantModal()"
|
|
(editMerchantRequested)="onEditMerchantRequested($event)"
|
|
(editConfigRequested)="onEditConfigRequested($event)"
|
|
(back)="backToList()"
|
|
/>
|
|
}
|
|
} @else {
|
|
<div class="alert alert-warning text-center">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
Aucun marchand sélectionné
|
|
</div>
|
|
}
|
|
</ng-template>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content" [ngbNavOutlet]="configsNav"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal de création de marchand -->
|
|
<ng-template #createMerchantModal let-modal>
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">
|
|
<ng-icon name="lucidePlus" class="me-2"></ng-icon>
|
|
Créer un nouveau marchand
|
|
</h4>
|
|
<button
|
|
type="button"
|
|
class="btn-close"
|
|
(click)="modal.dismiss()"
|
|
[disabled]="creatingMerchant"
|
|
aria-label="Fermer"
|
|
></button>
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
<!-- Message d'erreur -->
|
|
@if (createMerchantError) {
|
|
<div class="alert alert-danger d-flex align-items-center">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
<div>{{ createMerchantError }}</div>
|
|
</div>
|
|
}
|
|
|
|
<form (ngSubmit)="createMerchant()" #merchantForm="ngForm">
|
|
<div class="row g-3">
|
|
<!-- Informations de base du marchand -->
|
|
<div class="col-md-6">
|
|
<label class="form-label">
|
|
Nom du marchand <span class="text-danger">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="Ex: Boutique ABC"
|
|
[(ngModel)]="newMerchant.name"
|
|
name="name"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
#name="ngModel"
|
|
>
|
|
@if (name.invalid && name.touched) {
|
|
<div class="text-danger small">
|
|
Le nom du marchand est requis
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="form-group logo-upload-section">
|
|
<label class="form-label">
|
|
<ng-icon name="lucideImage" class="me-1"></ng-icon>
|
|
Logo du marchand (optionnel)
|
|
</label>
|
|
|
|
<div class="logo-upload-container">
|
|
<!-- Zone de prévisualisation -->
|
|
<div class="logo-preview-area">
|
|
@if (logoPreviewUrl) {
|
|
<!-- Image sélectionnée -->
|
|
<div class="logo-preview">
|
|
<img [src]="logoPreviewUrl" alt="Prévisualisation du logo">
|
|
<button
|
|
type="button"
|
|
class="btn-remove-preview"
|
|
(click)="removeSelectedLogo()"
|
|
[disabled]="creatingMerchant || uploadingLogo"
|
|
title="Supprimer"
|
|
>
|
|
<ng-icon name="lucideX"></ng-icon>
|
|
</button>
|
|
</div>
|
|
} @else {
|
|
<!-- Placeholder -->
|
|
<div class="logo-placeholder">
|
|
<ng-icon name="lucideImage" size="48" class="text-muted mb-2"></ng-icon>
|
|
<p class="text-muted mb-0">Aucun logo sélectionné</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Bouton de sélection -->
|
|
<div class="logo-upload-actions">
|
|
<input
|
|
type="file"
|
|
id="logoInput"
|
|
accept="image/jpeg,image/jpg,image/png,image/gif,image/webp,image/svg+xml"
|
|
(change)="onLogoSelected($event)"
|
|
[disabled]="creatingMerchant || uploadingLogo"
|
|
style="display: none;"
|
|
/>
|
|
<label
|
|
for="logoInput"
|
|
class="btn btn-outline-primary btn-select-logo"
|
|
[class.disabled]="creatingMerchant || uploadingLogo"
|
|
>
|
|
<ng-icon name="lucideUpload" class="me-1"></ng-icon>
|
|
{{ logoPreviewUrl ? 'Changer le logo' : 'Sélectionner un logo' }}
|
|
</label>
|
|
|
|
<small class="text-muted d-block mt-2">
|
|
<ng-icon name="lucideInfo" size="12" class="me-1"></ng-icon>
|
|
Formats acceptés : JPG, PNG, GIF, WebP, SVG | Taille max : 5MB
|
|
</small>
|
|
|
|
@if (selectedLogoFile) {
|
|
<div class="mt-2">
|
|
<small class="badge bg-info">
|
|
<ng-icon name="lucideFile" size="12" class="me-1"></ng-icon>
|
|
{{ selectedLogoFile.name }} ({{ formatFileSize(selectedLogoFile.size) }})
|
|
</small>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Erreur upload logo -->
|
|
@if (logoUploadError) {
|
|
<div class="alert alert-danger mt-2">
|
|
<ng-icon name="lucideAlertCircle" class="me-1"></ng-icon>
|
|
{{ logoUploadError }}
|
|
</div>
|
|
}
|
|
|
|
<!-- Indicateur d'upload -->
|
|
@if (uploadingLogo) {
|
|
<div class="upload-progress">
|
|
<div class="spinner-border spinner-border-sm" role="status">
|
|
<span class="visually-hidden">Upload en cours...</span>
|
|
</div>
|
|
<span class="ms-2">Upload du logo en cours...</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Description</label>
|
|
<textarea
|
|
class="form-control"
|
|
placeholder="Description du marchand"
|
|
[(ngModel)]="newMerchant.description"
|
|
name="description"
|
|
[disabled]="creatingMerchant"
|
|
rows="2"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">
|
|
Adresse <span class="text-danger">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="Adresse complète"
|
|
[(ngModel)]="newMerchant.adresse"
|
|
name="adresse"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
#adresse="ngModel"
|
|
>
|
|
@if (adresse.invalid && adresse.touched) {
|
|
<div class="text-danger small">
|
|
L'adresse est requise
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">
|
|
Téléphone <span class="text-danger">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
placeholder="+XX X XX XX XX XX"
|
|
[(ngModel)]="newMerchant.phone"
|
|
name="phone"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
#phone="ngModel"
|
|
>
|
|
@if (phone.invalid && phone.touched) {
|
|
<div class="text-danger small">
|
|
Le téléphone est requis
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Section Contacts Techniques -->
|
|
<div class="col-12">
|
|
<div class="border-top pt-3">
|
|
<h6 class="mb-3">
|
|
<ng-icon name="lucideUsers" class="me-2"></ng-icon>
|
|
Contacts Techniques
|
|
</h6>
|
|
|
|
@if (!newMerchant.technicalContacts || newMerchant.technicalContacts.length === 0) {
|
|
<div class="alert alert-warning">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
Au moins un contact technique est requis
|
|
</div>
|
|
}
|
|
|
|
|
|
@if(newMerchant.technicalContacts) {
|
|
<!-- Liste des contacts -->
|
|
@for (contact of newMerchant.technicalContacts; track $index; let i = $index) {
|
|
<div class="card mb-3">
|
|
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
|
<span>Contact {{ i + 1 }}</span>
|
|
@if (newMerchant.technicalContacts.length > 1) {
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-outline-danger"
|
|
(click)="removeTechnicalContact(i)"
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
Supprimer
|
|
</button>
|
|
}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Prénom <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="contact.firstName"
|
|
[name]="'firstName_' + i"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Nom <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="contact.lastName"
|
|
[name]="'lastName_' + i"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Téléphone <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="contact.phone"
|
|
[name]="'phone_' + i"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Email <span class="text-danger">*</span></label>
|
|
<input
|
|
type="email"
|
|
class="form-control"
|
|
[(ngModel)]="contact.email"
|
|
[name]="'email_' + i"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-sm"
|
|
(click)="addTechnicalContact()"
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
<ng-icon name="lucideUserPlus" class="me-1"></ng-icon>
|
|
Ajouter un contact
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section Configurations -->
|
|
<div class="col-12">
|
|
<div class="border-top pt-3">
|
|
<h6 class="mb-3">
|
|
<ng-icon name="lucideSettings" class="me-2"></ng-icon>
|
|
Configurations Techniques
|
|
</h6>
|
|
|
|
@if (!newMerchant.configs || newMerchant.configs.length === 0) {
|
|
<div class="alert alert-warning">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
Au moins une configuration est requise
|
|
</div>
|
|
}
|
|
|
|
@if(newMerchant.configs) {
|
|
<!-- Liste des configurations -->
|
|
@for (config of newMerchant.configs; track $index; let i = $index) {
|
|
<div class="card mb-3">
|
|
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
|
<span>Configuration {{ i + 1 }}</span>
|
|
@if (newMerchant.configs.length > 1) {
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-outline-danger"
|
|
(click)="removeConfig(i)"
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
Supprimer
|
|
</button>
|
|
}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Type <span class="text-danger">*</span></label>
|
|
<select
|
|
class="form-select"
|
|
[(ngModel)]="config.name"
|
|
[name]="'configType_' + i"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
<option value="" disabled>Sélectionnez un type</option>
|
|
@for (type of configTypes; track type.name) {
|
|
<option [value]="type.name">{{ type.label }}</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Opérateur <span class="text-danger">*</span></label>
|
|
<select
|
|
class="form-select"
|
|
[(ngModel)]="config.operatorId"
|
|
[name]="'operatorId_' + i"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
<option value="" disabled>Sélectionnez un opérateur</option>
|
|
@for (operator of operators; track operator.id) {
|
|
<option [value]="operator.id">{{ operator.name }}</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">Valeur <span class="text-danger">*</span></label>
|
|
<textarea
|
|
class="form-control"
|
|
[(ngModel)]="config.value"
|
|
[name]="'value_' + i"
|
|
required
|
|
[disabled]="creatingMerchant"
|
|
rows="2"
|
|
placeholder="Valeur de configuration"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-sm"
|
|
(click)="addConfig()"
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
<ng-icon name="lucideSettings" class="me-1"></ng-icon>
|
|
Ajouter une configuration
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer mt-4">
|
|
<button
|
|
type="button"
|
|
class="btn btn-light"
|
|
(click)="modal.dismiss()"
|
|
[disabled]="creatingMerchant"
|
|
>
|
|
<ng-icon name="lucideX" class="me-1"></ng-icon>
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
class="btn btn-primary"
|
|
[disabled]="!merchantForm.form.valid || creatingMerchant"
|
|
>
|
|
@if (creatingMerchant) {
|
|
<div class="spinner-border spinner-border-sm me-2" role="status">
|
|
<span class="visually-hidden">Chargement...</span>
|
|
</div>
|
|
Création...
|
|
} @else {
|
|
<ng-icon name="lucidePlus" class="me-1"></ng-icon>
|
|
Créer le marchand
|
|
}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</ng-template>
|
|
|
|
<!-- Modal d'édition de marchand -->
|
|
<ng-template #editMerchantModal let-modal>
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">
|
|
<ng-icon name="lucideEdit" class="me-2"></ng-icon>
|
|
Modifier le marchand
|
|
</h4>
|
|
<button
|
|
type="button"
|
|
class="btn-close"
|
|
(click)="modal.dismiss()"
|
|
[disabled]="updatingMerchant"
|
|
aria-label="Fermer"
|
|
></button>
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
<!-- Message d'erreur -->
|
|
@if (updateMerchantError) {
|
|
<div class="alert alert-danger d-flex align-items-center">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
<div>{{ updateMerchantError }}</div>
|
|
</div>
|
|
}
|
|
|
|
@if (selectedMerchantForEdit) {
|
|
<form (ngSubmit)="updateMerchant()" #editForm="ngForm">
|
|
|
|
<!-- INFORMATIONS GÉNÉRALES -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12">
|
|
<h6 class="border-bottom pb-2 text-primary">
|
|
<ng-icon name="lucideBuilding" class="me-2"></ng-icon>
|
|
Informations Générales
|
|
</h6>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Nom du marchand <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="selectedMerchantForEdit.name"
|
|
name="name"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
placeholder="Ex: Boutique ABC"
|
|
>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="form-group logo-edit-section">
|
|
<label class="form-label">
|
|
<ng-icon name="lucideImage" class="me-1"></ng-icon>
|
|
Logo du marchand
|
|
</label>
|
|
|
|
<div class="logo-edit-container">
|
|
<!-- Logo actuel ou nouveau -->
|
|
<div class="logo-display-area">
|
|
@if (editLogoPreviewUrl) {
|
|
<!-- Nouveau logo sélectionné -->
|
|
<div class="logo-preview">
|
|
<img [src]="editLogoPreviewUrl" alt="Nouveau logo">
|
|
<div class="logo-badge badge-new">Nouveau</div>
|
|
<button
|
|
type="button"
|
|
class="btn-remove-preview"
|
|
(click)="cancelEditLogo()"
|
|
[disabled]="updatingMerchant || uploadingLogo"
|
|
title="Annuler"
|
|
>
|
|
<ng-icon name="lucideX"></ng-icon>
|
|
</button>
|
|
</div>
|
|
} @else if (currentLogoUrl) {
|
|
<!-- Logo actuel -->
|
|
<div class="logo-preview">
|
|
<img [src]="currentLogoUrl" alt="Logo actuel">
|
|
<div class="logo-badge badge-current">Actuel</div>
|
|
</div>
|
|
} @else {
|
|
<!-- Pas de logo -->
|
|
<div class="logo-placeholder">
|
|
<ng-icon name="lucideImage" size="48" class="text-muted mb-2"></ng-icon>
|
|
<p class="text-muted mb-0">Aucun logo</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="logo-edit-actions">
|
|
<input
|
|
type="file"
|
|
id="editLogoInput"
|
|
accept="image/jpeg,image/jpg,image/png,image/gif,image/webp,image/svg+xml"
|
|
(change)="onEditLogoSelected($event)"
|
|
[disabled]="updatingMerchant || uploadingLogo"
|
|
style="display: none;"
|
|
/>
|
|
<label
|
|
for="editLogoInput"
|
|
class="btn btn-outline-primary btn-change-logo"
|
|
[class.disabled]="updatingMerchant || uploadingLogo"
|
|
>
|
|
<ng-icon name="lucideRefreshCw" class="me-1"></ng-icon>
|
|
{{ currentLogoUrl ? 'Changer le logo' : 'Ajouter un logo' }}
|
|
</label>
|
|
|
|
@if (currentLogoUrl && !editLogoPreviewUrl) {
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-danger btn-remove-logo"
|
|
(click)="selectedMerchantForEdit!.logo = ''; currentLogoUrl = null"
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
Supprimer le logo
|
|
</button>
|
|
}
|
|
|
|
<small class="text-muted d-block mt-2">
|
|
<ng-icon name="lucideInfo" size="12" class="me-1"></ng-icon>
|
|
Formats : JPG, PNG, GIF, WebP, SVG | Max : 5MB
|
|
</small>
|
|
|
|
@if (editLogoFile) {
|
|
<div class="mt-2">
|
|
<small class="badge bg-info">
|
|
<ng-icon name="lucideFile" size="12" class="me-1"></ng-icon>
|
|
{{ editLogoFile.name }} ({{ formatFileSize(editLogoFile.size) }})
|
|
</small>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Indicateur d'upload -->
|
|
@if (uploadingLogo) {
|
|
<div class="upload-progress">
|
|
<div class="spinner-border spinner-border-sm" role="status">
|
|
<span class="visually-hidden">Upload en cours...</span>
|
|
</div>
|
|
<span class="ms-2">Upload du nouveau logo en cours...</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Description</label>
|
|
<textarea
|
|
class="form-control"
|
|
[(ngModel)]="selectedMerchantForEdit.description"
|
|
name="description"
|
|
[disabled]="updatingMerchant"
|
|
rows="2"
|
|
placeholder="Description du marchand"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Adresse <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="selectedMerchantForEdit.adresse"
|
|
name="adresse"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
placeholder="Adresse complète"
|
|
>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label">Téléphone <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="selectedMerchantForEdit.phone"
|
|
name="phone"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
placeholder="+XX X XX XX XX XX"
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CONFIGURATIONS TECHNIQUES -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center border-bottom pb-2">
|
|
<h6 class="mb-0 text-primary">
|
|
<ng-icon name="lucideSettings" class="me-2"></ng-icon>
|
|
Configurations Techniques
|
|
</h6>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-sm"
|
|
(click)="addConfigInEdit()"
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<ng-icon name="lucidePlus" class="me-1"></ng-icon>
|
|
Ajouter une configuration
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!selectedMerchantForEdit.configs || selectedMerchantForEdit.configs.length === 0) {
|
|
<div class="col-12">
|
|
<div class="alert alert-warning">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
Au moins une configuration est requise
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Liste des configurations -->
|
|
@for (config of selectedMerchantForEdit.configs; track trackByConfigId($index, config); let i = $index) {
|
|
<div class="col-12">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-light py-2 d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<ng-icon [name]="getConfigTypeIconSafe(config.name)" class="me-2 text-primary"></ng-icon>
|
|
<span class="fw-semibold">Configuration {{ i + 1 }}</span>
|
|
</div>
|
|
@if (selectedMerchantForEdit.configs.length > 1) {
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-outline-danger"
|
|
(click)="removeConfigInEdit(i)"
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
Supprimer
|
|
</button>
|
|
}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Type <span class="text-danger">*</span></label>
|
|
<select
|
|
class="form-select"
|
|
[(ngModel)]="config.name"
|
|
[name]="'editConfigType_' + i"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<option value="" disabled>Sélectionnez un type</option>
|
|
@for (type of configTypes; track type.name) {
|
|
<option [value]="type.name">{{ type.label }}</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Opérateur <span class="text-danger">*</span></label>
|
|
<select
|
|
class="form-select"
|
|
[(ngModel)]="config.operatorId"
|
|
[name]="'editOperatorId_' + i"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<option value="" disabled>Sélectionnez un opérateur</option>
|
|
@for (operator of operators; track operator.id) {
|
|
<option [value]="operator.id">{{ operator.name }}</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">Valeur <span class="text-danger">*</span></label>
|
|
<textarea
|
|
class="form-control font-monospace"
|
|
[(ngModel)]="config.value"
|
|
[name]="'editValue_' + i"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
rows="3"
|
|
placeholder="Valeur de configuration"
|
|
></textarea>
|
|
@if (isSensitiveConfig(config)) {
|
|
<div class="form-text text-warning">
|
|
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
|
Cette configuration contient des informations sensibles
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- CONTACTS TECHNIQUES -->
|
|
<div class="row g-3">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center border-bottom pb-2">
|
|
<h6 class="mb-0 text-primary">
|
|
<ng-icon name="lucideUsers" class="me-2"></ng-icon>
|
|
Contacts Techniques
|
|
</h6>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-sm"
|
|
(click)="addTechnicalContactInEdit()"
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<ng-icon name="lucideUserPlus" class="me-1"></ng-icon>
|
|
Ajouter un contact
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!selectedMerchantForEdit.technicalContacts || selectedMerchantForEdit.technicalContacts.length === 0) {
|
|
<div class="col-12">
|
|
<div class="alert alert-warning">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
Au moins un contact technique est requis
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Liste des contacts techniques -->
|
|
@for (contact of selectedMerchantForEdit.technicalContacts; track trackByContactId($index, contact); let i = $index) {
|
|
<div class="col-12">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-light py-2 d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<ng-icon name="lucideUser" class="me-2 text-primary"></ng-icon>
|
|
<span class="fw-semibold">Contact {{ i + 1 }}</span>
|
|
</div>
|
|
@if (selectedMerchantForEdit.technicalContacts.length > 1) {
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-outline-danger"
|
|
(click)="removeTechnicalContactInEdit(i)"
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
Supprimer
|
|
</button>
|
|
}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Prénom <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="contact.firstName"
|
|
[name]="'editFirstName_' + i"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
placeholder="Prénom"
|
|
>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Nom <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="contact.lastName"
|
|
[name]="'editLastName_' + i"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
placeholder="Nom"
|
|
>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Téléphone <span class="text-danger">*</span></label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
[(ngModel)]="contact.phone"
|
|
[name]="'editPhone_' + i"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
placeholder="+XX X XX XX XX XX"
|
|
>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Email <span class="text-danger">*</span></label>
|
|
<input
|
|
type="email"
|
|
class="form-control"
|
|
[(ngModel)]="contact.email"
|
|
[name]="'editEmail_' + i"
|
|
required
|
|
[disabled]="updatingMerchant"
|
|
placeholder="email@exemple.com"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="modal-footer mt-4 border-top pt-3">
|
|
<button
|
|
type="button"
|
|
class="btn btn-light"
|
|
(click)="modal.dismiss()"
|
|
[disabled]="updatingMerchant"
|
|
>
|
|
<ng-icon name="lucideX" class="me-1"></ng-icon>
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
class="btn btn-primary"
|
|
[disabled]="!editForm.form.valid || updatingMerchant ||
|
|
!selectedMerchantForEdit.technicalContacts.length ||
|
|
!selectedMerchantForEdit.configs.length"
|
|
>
|
|
@if (updatingMerchant) {
|
|
<div class="spinner-border spinner-border-sm me-2" role="status">
|
|
<span class="visually-hidden">Chargement...</span>
|
|
</div>
|
|
Mise à jour...
|
|
} @else {
|
|
<ng-icon name="lucideSave" class="me-1"></ng-icon>
|
|
Enregistrer les modifications
|
|
}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
} @else {
|
|
<div class="alert alert-warning text-center">
|
|
<ng-icon name="lucideAlertTriangle" class="me-2"></ng-icon>
|
|
Aucun marchand sélectionné pour modification
|
|
</div>
|
|
}
|
|
</div>
|
|
</ng-template>
|
|
|
|
<!-- Modal de confirmation de suppression -->
|
|
<ng-template #deleteMerchantModal let-modal>
|
|
<div class="modal-header">
|
|
<h4 class="modal-title text-danger">
|
|
<ng-icon name="lucideTrash2" class="me-2"></ng-icon>
|
|
Confirmer la suppression
|
|
</h4>
|
|
<button
|
|
type="button"
|
|
class="btn-close"
|
|
(click)="modal.dismiss()"
|
|
aria-label="Fermer"
|
|
></button>
|
|
</div>
|
|
|
|
<div class="modal-body text-center">
|
|
<div class="mb-4">
|
|
<div class="avatar-lg mx-auto mb-3 bg-danger bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
|
|
<ng-icon name="lucideStore" class="text-danger" style="font-size: 2rem;"></ng-icon>
|
|
</div>
|
|
<h5 class="text-danger mb-2">Êtes-vous sûr de vouloir supprimer ce marchand ?</h5>
|
|
<p class="text-muted mb-0">
|
|
Cette action est irréversible. Toutes les configurations associées seront également supprimées.
|
|
</p>
|
|
</div>
|
|
|
|
@if (selectedMerchantForDelete) {
|
|
<div class="alert alert-warning">
|
|
<div class="d-flex align-items-start">
|
|
<ng-icon name="lucideAlertTriangle" class="me-2 mt-1 text-warning"></ng-icon>
|
|
<div>
|
|
<strong>Marchand :</strong> {{ selectedMerchantForDelete.name }}<br>
|
|
<strong>Adresse :</strong> {{ selectedMerchantForDelete.adresse }}<br>
|
|
<strong>Téléphone :</strong> {{ selectedMerchantForDelete.phone }}<br>
|
|
<strong>Configurations :</strong> {{ selectedMerchantForDelete.configs.length || 0 }}<br>
|
|
<strong>Contacts techniques :</strong> {{ selectedMerchantForDelete.technicalContacts.length || 0 }}<br>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
} @else {
|
|
<div class="alert alert-warning">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
Aucun marchand sélectionné pour la suppression
|
|
</div>
|
|
}
|
|
|
|
<!-- Message d'erreur -->
|
|
@if (deleteMerchantError) {
|
|
<div class="alert alert-danger d-flex align-items-center">
|
|
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
|
<div>{{ deleteMerchantError }}</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button
|
|
type="button"
|
|
class="btn btn-light"
|
|
(click)="modal.dismiss()"
|
|
[disabled]="deletingMerchant"
|
|
>
|
|
<ng-icon name="lucideX" class="me-1"></ng-icon>
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-danger"
|
|
(click)="confirmDeleteMerchant()"
|
|
[disabled]="deletingMerchant || !selectedMerchantForDelete || !canDeleteMerchants"
|
|
>
|
|
@if (deletingMerchant) {
|
|
<div class="spinner-border spinner-border-sm me-2" role="status">
|
|
<span class="visually-hidden">Suppression...</span>
|
|
</div>
|
|
Suppression...
|
|
} @else {
|
|
<ng-icon name="lucideTrash2" class="me-1"></ng-icon>
|
|
Supprimer définitivement
|
|
}
|
|
</button>
|
|
</div>
|
|
</ng-template>
|