diff --git a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css
index 2532832..e9b466d 100644
--- a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css
+++ b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css
@@ -76,6 +76,24 @@
font-size: 40px;
}
+/* Bouton de suppression (croix rouge) */
+.carousel-delete-btn {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 4px;
+ padding: 2px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
+}
+
+.carousel-delete-btn mat-icon {
+ font-size: 20px;
+ width: 20px;
+ height: 20px;
+ color: #e53935;
+}
+
/* Bandeau de vignettes */
.carousel-thumbs {
@@ -85,15 +103,42 @@
}
.thumb-item {
+ position: relative;
width: 64px;
height: 64px;
border-radius: 4px;
- overflow: hidden;
+ overflow: hidden; /* tu peux laisser comme ça */
border: 2px solid transparent;
flex: 0 0 auto;
cursor: pointer;
}
+/* Bouton de suppression sur les vignettes */
+.thumb-delete-btn {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+
+ width: 18px;
+ height: 18px;
+ min-width: 18px;
+ padding: 0;
+
+ line-height: 18px;
+
+ background: transparent;
+ border-radius: 0;
+ box-shadow: none;
+}
+
+.thumb-delete-btn mat-icon {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+
+ color: #e53935; /* rouge discret mais lisible */
+}
+
.thumb-item.active {
border-color: #1976d2;
}
diff --git a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html
index a71ddb5..dec97b9 100644
--- a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html
+++ b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html
@@ -31,6 +31,23 @@
[disabled]="carouselItems.length <= 1">
chevron_right
+
+
+ @if (carouselItems.length && !carouselItems[currentIndex].isPlaceholder) {
+
+ }
+
+
+
@@ -40,6 +57,14 @@
[class.active]="i === currentIndex"
(click)="onThumbClick(i)">
@if (!item.isPlaceholder) {
+
+
+
+
} @else {
diff --git a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.ts b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.ts
index 15f7610..eb47b52 100644
--- a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.ts
+++ b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.ts
@@ -1,11 +1,11 @@
import {Component, Inject, OnInit, inject, OnDestroy} from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
-import { MatFormField, MatLabel } from '@angular/material/form-field';
-import { MatInput } from '@angular/material/input';
-import { MatSelectModule } from '@angular/material/select';
-import { MatCheckbox } from '@angular/material/checkbox';
-import { MatButton, MatIconButton } from '@angular/material/button';
+import {CommonModule} from '@angular/common';
+import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
+import {MatFormField, MatLabel} from '@angular/material/form-field';
+import {MatInput} from '@angular/material/input';
+import {MatSelectModule} from '@angular/material/select';
+import {MatCheckbox} from '@angular/material/checkbox';
+import {MatButton, MatIconButton} from '@angular/material/button';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -13,13 +13,13 @@ import {
MatDialogContent,
MatDialogTitle
} from '@angular/material/dialog';
-import { MatIcon } from '@angular/material/icon';
+import {MatIcon} from '@angular/material/icon';
-import { catchError, forkJoin, of, Observable } from 'rxjs';
+import {catchError, forkJoin, of, Observable} from 'rxjs';
-import { PsItem } from '../../interfaces/ps-item';
-import { ProductListItem } from '../../interfaces/product-list-item';
-import { PrestashopService } from '../../services/prestashop.serivce';
+import {PsItem} from '../../interfaces/ps-item';
+import {ProductListItem} from '../../interfaces/product-list-item';
+import {PrestashopService} from '../../services/prestashop.serivce';
export type ProductDialogData = {
mode: 'create' | 'edit';
@@ -48,7 +48,8 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
constructor(
@Inject(MAT_DIALOG_DATA) public data: ProductDialogData,
private readonly dialogRef: MatDialogRef
- ) {}
+ ) {
+ }
mode!: 'create' | 'edit';
categories: PsItem[] = [];
@@ -167,11 +168,11 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
const qty$ = this.ps.getProductQuantity(r.id).pipe(catchError(() => of(0)));
const imgs$ = this.ps.getProductImageUrls(r.id).pipe(catchError(() => of([])));
const flags$ = this.ps.getProductFlags(r.id).pipe(
- catchError(() => of({ complete: false, hasManual: false, conditionLabel: undefined }))
+ catchError(() => of({complete: false, hasManual: false, conditionLabel: undefined}))
);
- forkJoin({ details: details$, qty: qty$, imgs: imgs$, flags: flags$ })
- .subscribe(({ details, qty, imgs, flags }) => {
+ forkJoin({details: details$, qty: qty$, imgs: imgs$, flags: flags$})
+ .subscribe(({details, qty, imgs, flags}) => {
const ttc = this.toTtc(details.priceHt ?? 0);
const baseDesc = this.cleanForTextarea(details.description ?? '');
this.lastLoadedDescription = baseDesc;
@@ -203,7 +204,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
const fl = (ev.target as HTMLInputElement).files;
// Nettoyage des anciens objectURL
- for(let url of this.previewUrls) {
+ for (let url of this.previewUrls) {
URL.revokeObjectURL(url);
}
this.previewUrls = [];
@@ -224,12 +225,12 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
private buildCarousel() {
const items: CarouselItem[] = [
- ...this.existingImageUrls.map(u => ({ src: u, isPlaceholder: false })),
- ...this.previewUrls.map(u => ({ src: u, isPlaceholder: false }))
+ ...this.existingImageUrls.map(u => ({src: u, isPlaceholder: false})),
+ ...this.previewUrls.map(u => ({src: u, isPlaceholder: false}))
];
// placeholder en dernier
- items.push({ src: '', isPlaceholder: true });
+ items.push({src: '', isPlaceholder: true});
this.carouselItems = items;
if (!this.carouselItems.length) {
@@ -261,7 +262,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
}
}
- // -------- Save / close inchangés (à part dto.images) --------
+ // -------- Save / close --------
save() {
if (this.form.invalid) return;
@@ -297,6 +298,82 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
});
}
+ /** Extrait l'id_image depuis une URL FO Presta (.../img/p/.../.jpg) */
+ private extractImageIdFromUrl(url: string): number | null {
+ const m = /\/(\d+)\.(?:jpg|jpeg|png|gif)$/i.exec(url);
+ if (!m) return null;
+ const id = Number(m[1]);
+ return Number.isFinite(id) ? id : null;
+ }
+
+ /** Suppression générique d'une image à l'index donné (carrousel + vignettes) */
+ private deleteImageAtIndex(idx: number) {
+ if (!this.carouselItems.length) return;
+
+ const item = this.carouselItems[idx];
+ if (!item || item.isPlaceholder) return;
+
+ const existingCount = this.existingImageUrls.length;
+
+ // --- Cas 1 : image existante (déjà chez Presta) ---
+ if (idx < existingCount) {
+ if (!this.productRow) return; // sécurité
+
+ const url = this.existingImageUrls[idx];
+ const imageId = this.extractImageIdFromUrl(url);
+ if (!imageId) {
+ alert('Impossible de déterminer l’ID de l’image à supprimer.');
+ return;
+ }
+
+ if (!confirm('Supprimer cette image du produit ?')) return;
+
+ this.ps.deleteProductImage(this.productRow.id, imageId).subscribe({
+ next: () => {
+ // On la retire du tableau local et on reconstruit le carrousel
+ this.existingImageUrls.splice(idx, 1);
+ this.buildCarousel();
+
+ // Repositionnement de l’index si nécessaire
+ if (this.currentIndex >= this.carouselItems.length - 1) {
+ this.currentIndex = Math.max(0, this.carouselItems.length - 2);
+ }
+ },
+ error: (e: unknown) => {
+ alert('Erreur lors de la suppression de l’image : ' + (e instanceof Error ? e.message : String(e)));
+ }
+ });
+
+ return;
+ }
+
+ // --- Cas 2 : image locale (nouvelle) ---
+ const localIdx = idx - existingCount;
+ if (localIdx >= 0 && localIdx < this.previewUrls.length) {
+ if (!confirm('Retirer cette image de la sélection ?')) return;
+
+ this.previewUrls.splice(localIdx, 1);
+ this.images.splice(localIdx, 1);
+ this.buildCarousel();
+
+ if (this.currentIndex >= this.carouselItems.length - 1) {
+ this.currentIndex = Math.max(0, this.carouselItems.length - 2);
+ }
+ }
+ }
+
+ // utilisée par la grande image
+ onDeleteCurrentImage() {
+ if (!this.carouselItems.length) return;
+ this.deleteImageAtIndex(this.currentIndex);
+ }
+
+ // utilisée par la croix sur une vignette
+ onDeleteThumb(index: number, event: MouseEvent) {
+ event.stopPropagation();
+ this.deleteImageAtIndex(index);
+ }
+
close() {
this.dialogRef.close(false);
}