feat: add categories and manufacturers CRUD components; implement form handling and image upload functionality

This commit is contained in:
Vincent Guillet
2025-11-10 16:29:42 +01:00
parent b84a829a82
commit a849a4dd15
22 changed files with 968 additions and 47 deletions

View File

@@ -33,6 +33,8 @@ import {ConditionService} from '../../services/app/condition.service';
import {Condition} from '../../interfaces/condition';
import {ProductService} from '../../services/app/product.service';
import {CdkTextareaAutosize} from '@angular/cdk/text-field';
import {ImageService} from '../../services/app/image.service';
import {ProductImageService} from '../../services/app/product_images.service';
@Component({
selector: 'app-add-product',
@@ -67,6 +69,11 @@ export class AddProductComponent implements OnInit, OnDestroy {
isSubmitted = false;
isLoading = false;
imageFile: File | null = null;
imagePreview: string | null = null;
private imageUploadSubscription: Subscription | null = null;
private imageLinkSubscription: Subscription | null = null;
brands: Brand[] = [];
platforms: Platform[] = [];
categories: Category[] = [];
@@ -89,7 +96,9 @@ export class AddProductComponent implements OnInit, OnDestroy {
private readonly platformService = inject(PlatformService);
private readonly categoryService = inject(CategoryService);
private readonly conditionService = inject(ConditionService);
private readonly imageService = inject(ImageService)
private readonly productService = inject(ProductService);
private readonly productImageService = inject(ProductImageService);
private readonly router: Router = inject(Router);
@@ -278,60 +287,117 @@ export class AddProductComponent implements OnInit, OnDestroy {
this.platformSubscription?.unsubscribe();
this.categorySubscription?.unsubscribe();
this.conditionSubscription?.unsubscribe();
this.imageUploadSubscription?.unsubscribe();
this.imageLinkSubscription?.unsubscribe();
}
onFileSelected(event: Event) {
const input = event.target as HTMLInputElement;
if (!input.files || input.files.length === 0) {
this.imageFile = null;
this.imagePreview = null;
return;
}
const file = input.files[0];
this.imageFile = file;
const reader = new FileReader();
reader.onload = () => {
this.imagePreview = String(reader.result);
};
reader.readAsDataURL(file);
}
onProductAdd() {
this.isSubmitted = true;
if (this.addProductForm.valid) {
this.isLoading = true;
const raw = this.addProductForm.value;
if (!this.addProductForm.valid) return;
const priceStr = raw.price ?? '';
const priceNum = Number(String(priceStr).replace(',', '.').trim());
if (Number.isNaN(priceNum)) {
this.isLoading = false;
this.addProductForm.get('price')?.setErrors({pattern: true});
return;
}
this.isLoading = true;
const raw = this.addProductForm.value;
const quantityNum = Number(raw.quantity);
const brandId = raw.brand;
const brandObj = this.brands.find(b => String(b.id) === String(brandId)) ?? {id: brandId, name: undefined};
const platformId = raw.platform;
const foundPlatform = this.platforms.find(p => String(p.id) === String(platformId));
const platformObj = {
...(foundPlatform ?? {id: platformId, name: undefined}),
brand: foundPlatform?.brand ? (typeof foundPlatform.brand === 'object' ? foundPlatform.brand : (this.brands.find(b => String(b.id) === String(foundPlatform.brand)) ?? brandObj)) : brandObj
};
const payload = {
...raw,
price: priceNum,
quantity: quantityNum,
brand: brandObj,
platform: platformObj
};
this.addProductSubscription = this.productService.add(payload).subscribe({
next: (response: any) => {
console.log("Product added successfully:", response);
this.addProductForm.reset();
this.isSubmitted = false;
alert("Produit ajouté avec succès !");
this.router.navigate(['/products']).then();
},
error: (error: any) => {
console.error("Error adding product:", error);
alert("Une erreur est survenue lors de l'ajout du produit.");
},
complete: () => {
this.isLoading = false;
}
});
// parsing price/quantity etc (même logique que l'original)
const priceStr = raw.price ?? '';
const priceNum = Number(String(priceStr).replace(',', '.').trim());
if (Number.isNaN(priceNum)) {
this.isLoading = false;
this.addProductForm.get('price')?.setErrors({pattern: true});
return;
}
const quantityNum = Number(raw.quantity);
const brandId = raw.brand;
const brandObj = this.brands.find(b => String(b.id) === String(brandId)) ?? {id: brandId, name: undefined};
const platformId = raw.platform;
const foundPlatform = this.platforms.find(p => String(p.id) === String(platformId));
const platformObj = {
...(foundPlatform ?? {id: platformId, name: undefined}),
brand: foundPlatform?.brand ? (typeof foundPlatform.brand === 'object' ? foundPlatform.brand : (this.brands.find(b => String(b.id) === String(foundPlatform.brand)) ?? brandObj)) : brandObj
};
const payload = {
...raw,
price: priceNum,
quantity: quantityNum,
brand: brandObj,
platform: platformObj
};
// 1) créer le produit
this.addProductSubscription = this.productService.add(payload).subscribe({
next: (createdProduct: any) => {
const productId = createdProduct?.id;
if (!this.imageFile) {
// pas d'image => fin
this.afterSuccessfulAdd();
return;
}
// 2) upload de l'image
this.imageUploadSubscription = this.imageService.add(this.imageFile).subscribe({
next: (uploadedImage: { id: any; }) => {
const imageId = uploadedImage?.id;
if (!productId || imageId == null) {
console.error('Missing productId or imageId after upload');
this.afterSuccessfulAdd(); // navigation quand même ou gérer l'erreur
return;
}
// 3) lier image <-> product
this.imageLinkSubscription = this.productImageService.link(productId, imageId).subscribe({
next: () => {
this.afterSuccessfulAdd();
},
error: (error: any) => {
console.error('Error linking image to product:', error);
alert('Produit ajouté, mais la liaison de l\'image a échoué.');
this.afterSuccessfulAdd();
}
});
},
error: (error: any) => {
console.error('Error uploading image:', error);
alert('Produit ajouté, mais l\'upload de l\'image a échoué.');
this.afterSuccessfulAdd();
}
});
},
error: (error: any) => {
console.error("Error adding product:", error);
alert("Une erreur est survenue lors de l'ajout du produit.");
this.isLoading = false;
}
});
}
private afterSuccessfulAdd() {
this.addProductForm.reset();
this.imageFile = null;
this.imagePreview = null;
this.isSubmitted = false;
this.isLoading = false;
alert("Produit ajouté avec succès !");
this.router.navigate(['/products']).then();
}
isFieldInvalid(fieldName: string): boolean {