feat: implement image carousel in product dialog; enhance image upload handling and preview functionality

This commit is contained in:
Vincent Guillet
2025-11-18 16:32:29 +01:00
parent d4ffcf0562
commit b756c9fa2d
7 changed files with 302 additions and 58 deletions

View File

@@ -1,11 +1,11 @@
import { Component, Inject, OnInit, inject } from '@angular/core';
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 } from '@angular/material/button';
import { MatButton, MatIconButton } from '@angular/material/button';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -13,6 +13,7 @@ import {
MatDialogContent,
MatDialogTitle
} from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon';
import { catchError, forkJoin, of, Observable } from 'rxjs';
@@ -26,6 +27,8 @@ export type ProductDialogData = {
productRow?: ProductListItem & { priceHt?: number };
};
type CarouselItem = { src: string; isPlaceholder: boolean };
@Component({
selector: 'app-ps-product-dialog',
standalone: true,
@@ -34,10 +37,11 @@ export type ProductDialogData = {
imports: [
CommonModule, ReactiveFormsModule,
MatFormField, MatLabel, MatInput, MatSelectModule, MatCheckbox,
MatButton, MatDialogActions, MatDialogContent, MatDialogTitle
MatButton, MatDialogActions, MatDialogContent, MatDialogTitle,
MatIcon, MatIconButton
]
})
export class PsProductDialogComponent implements OnInit {
export class PsProductDialogComponent implements OnInit, OnDestroy {
private readonly fb = inject(FormBuilder);
private readonly ps = inject(PrestashopService);
@@ -55,6 +59,13 @@ export class PsProductDialogComponent implements OnInit {
images: File[] = [];
existingImageUrls: string[] = [];
// Previews des fichiers nouvellement sélectionnés
previewUrls: string[] = [];
// items du carrousel (existants + nouveaux + placeholder)
carouselItems: CarouselItem[] = [];
currentIndex = 0;
// options possibles pour l'état (Neuf, Très bon état, etc.)
conditionOptions: string[] = [];
@@ -178,15 +189,80 @@ export class PsProductDialogComponent implements OnInit {
});
this.existingImageUrls = imgs;
this.buildCarousel();
});
} else {
// mode création : uniquement placeholder au début
this.buildCarousel();
}
}
// -------- Carrousel / gestion des fichiers --------
onFiles(ev: Event) {
const fl = (ev.target as HTMLInputElement).files;
this.images = fl ? Array.from(fl) : [];
// Nettoyage des anciens objectURL
for(let url of this.previewUrls) {
URL.revokeObjectURL(url);
}
this.previewUrls = [];
this.images = [];
if (fl) {
this.images = Array.from(fl);
this.previewUrls = this.images.map(f => URL.createObjectURL(f));
}
this.buildCarousel();
// si on a ajouté des images, se placer sur la première nouvelle
if (this.images.length && this.existingImageUrls.length + this.previewUrls.length > 0) {
this.currentIndex = this.existingImageUrls.length; // première nouvelle
}
}
private buildCarousel() {
const items: CarouselItem[] = [
...this.existingImageUrls.map(u => ({ src: u, isPlaceholder: false })),
...this.previewUrls.map(u => ({ src: u, isPlaceholder: false }))
];
// placeholder en dernier
items.push({ src: '', isPlaceholder: true });
this.carouselItems = items;
if (!this.carouselItems.length) {
this.currentIndex = 0;
} else if (this.currentIndex >= this.carouselItems.length) {
this.currentIndex = 0;
}
}
prev() {
if (!this.carouselItems.length) return;
this.currentIndex = (this.currentIndex - 1 + this.carouselItems.length) % this.carouselItems.length;
}
next() {
if (!this.carouselItems.length) return;
this.currentIndex = (this.currentIndex + 1) % this.carouselItems.length;
}
onThumbClick(index: number) {
if (index < 0 || index >= this.carouselItems.length) return;
this.currentIndex = index;
}
ngOnDestroy() {
// Nettoyage des objectURL
for (let url of this.previewUrls) {
URL.revokeObjectURL(url);
}
}
// -------- Save / close inchangés (à part dto.images) --------
save() {
if (this.form.invalid) return;
@@ -199,7 +275,7 @@ export class PsProductDialogComponent implements OnInit {
categoryId: +v.categoryId!,
manufacturerId: +v.manufacturerId!,
supplierId: +v.supplierId!,
images: this.images,
images: this.images, // toujours les fichiers sélectionnés
complete: !!v.complete,
hasManual: !!v.hasManual,
conditionLabel: v.conditionLabel || undefined,