Add image deletion functionality to product dialog carousel

This commit is contained in:
Vincent Guillet
2025-12-03 21:21:50 +01:00
parent 9763289c2f
commit 1a5d3a570a
3 changed files with 169 additions and 22 deletions

View File

@@ -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<PsProductDialogComponent>
) {}
) {
}
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<string[]>([])));
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/.../<id>.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 lID de limage à 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 lindex 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 limage : ' + (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);
}