import {Component, inject, OnDestroy, OnInit} from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, ValidatorFn, Validators } from "@angular/forms"; import {MatButton} from "@angular/material/button"; import { MatCard, MatCardActions, MatCardContent, MatCardHeader, MatCardTitle } from "@angular/material/card"; import {MatCheckbox} from "@angular/material/checkbox"; import {MatDivider} from "@angular/material/divider"; import {MatError, MatFormField, MatLabel} from "@angular/material/form-field"; import {MatInput} from "@angular/material/input"; import {MatProgressSpinner} from "@angular/material/progress-spinner"; import {MatOption, MatSelect} from '@angular/material/select'; import {Router, RouterLink} from '@angular/router'; import {Subscription} from 'rxjs'; import {BrandService} from '../../services/app/brand.service'; import {Brand} from '../../interfaces/brand'; import {PlatformService} from '../../services/app/platform.service'; import {Platform} from '../../interfaces/platform'; import {Category} from '../../interfaces/category'; import {CategoryService} from '../../services/app/category.service'; 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', standalone: true, imports: [ FormsModule, MatButton, MatCard, MatCardActions, MatCardContent, MatCardHeader, MatCardTitle, MatCheckbox, MatDivider, MatError, MatFormField, MatInput, MatLabel, MatProgressSpinner, ReactiveFormsModule, MatSelect, MatOption, RouterLink, CdkTextareaAutosize ], templateUrl: './add-product.component.html', styleUrl: './add-product.component.css' }) export class AddProductComponent implements OnInit, OnDestroy { addProductForm: FormGroup; 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[] = []; conditions: Condition[] = []; filteredBrands: Brand[] = []; filteredPlatforms: Platform[] = []; private addProductSubscription: Subscription | null = null; private brandControlSubscription: Subscription | null = null; private platformControlSubscription: Subscription | null = null; private brandSubscription: Subscription | null = null; private platformSubscription: Subscription | null = null; private categorySubscription: Subscription | null = null; private conditionSubscription: Subscription | null = null; private readonly brandService: BrandService = inject(BrandService); 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); constructor(private readonly formBuilder: FormBuilder) { this.addProductForm = this.formBuilder.group({ title: ['', [ Validators.required, Validators.minLength(3), Validators.maxLength(50), Validators.pattern(/^[\p{L}\p{N}\s]+$/u) ]], description: ['', [ Validators.required, Validators.minLength(10), Validators.maxLength(255), Validators.pattern(/^[\p{L}\p{N}\s]+$/u) ]], category: ['', [ Validators.required ]], condition: ['', [ Validators.required ]], // stocker des ids (string|number) dans les controls brand: ['', [ Validators.required ]], platform: ['', [ Validators.required ]], complete: [true], manual: [true], price: ['', [ Validators.required, Validators.pattern(/^\d+([.,]\d{1,2})?$/), this.priceRangeValidator(0, 9999) ]], quantity: ['', [ Validators.required, Validators.min(1), Validators.max(999), Validators.pattern(/^\d+$/) ]] }, ); } private normalizeIds>(items: T[] | undefined, idKey = 'id'): T[] { return (items || []).map((it, i) => ({ ...it, [idKey]: (it[idKey] ?? i) })); } private getPlatformBrandId(platform: any): string | number | undefined { if (!platform) return undefined; const maybe = platform.brand ?? platform['brand_id'] ?? platform['brandId']; if (maybe == null) return undefined; if (typeof maybe === 'object') { if (maybe.id != null) return maybe.id; if (maybe.name != null) { const found = this.brands.find(b => String(b.name).toLowerCase() === String(maybe.name).toLowerCase() || String(b.id) === String(maybe.name) ); return found?.id; } return undefined; } const asStr = String(maybe); const match = this.brands.find(b => String(b.id) === asStr || String(b.name).toLowerCase() === asStr.toLowerCase() ); return match?.id ?? maybe; } private priceRangeValidator(min: number, max: number): ValidatorFn { return (control: AbstractControl) => { const val = control.value; if (val === null || val === undefined || val === '') return null; const normalized = String(val).replace(',', '.').trim(); const num = Number.parseFloat(normalized); if (Number.isNaN(num)) return {pattern: true}; return (num < min || num > max) ? {range: {min, max, actual: num}} : null; }; } ngOnInit(): void { this.brandSubscription = this.brandService.getAll().subscribe({ next: (brands: Brand[]) => { this.brands = this.normalizeIds(brands, 'id'); this.filteredBrands = [...this.brands]; }, error: (error: any) => { console.error('Error fetching brands:', error); }, complete: () => { console.log('Finished fetching brands:', this.brands); } }); this.platformSubscription = this.platformService.getAll().subscribe({ next: (platforms: Platform[]) => { this.platforms = this.normalizeIds(platforms, 'id'); this.filteredPlatforms = [...this.platforms]; }, error: (error: any) => { console.error('Error fetching platforms:', error); }, complete: () => { console.log('Finished fetching platforms:', this.platforms); } }); this.categorySubscription = this.categoryService.getAll().subscribe({ next: (categories: Category[]) => { this.categories = this.normalizeIds(categories, 'id'); }, error: (error: any) => { console.error('Error fetching categories:', error); }, complete: () => { console.log('Finished fetching categories:', this.categories); } }); this.conditionSubscription = this.conditionService.getAll().subscribe({ next: (conditions: Condition[]) => { this.conditions = this.normalizeIds(conditions, 'id'); }, error: (error) => { console.error('Error fetching conditions:', error); }, complete: () => { console.log('Finished fetching conditions:', this.conditions); } }); const brandControl = this.addProductForm.get('brand'); const platformControl = this.addProductForm.get('platform'); this.brandControlSubscription = brandControl?.valueChanges.subscribe((brandId) => { if (brandId != null && brandId !== '') { const brandIdStr = String(brandId); this.filteredPlatforms = this.platforms.filter(p => { const pBid = this.getPlatformBrandId(p); return pBid != null && String(pBid) === brandIdStr; }); const curPlatformId = platformControl?.value; if (curPlatformId != null && !this.filteredPlatforms.some(p => String(p.id) === String(curPlatformId))) { platformControl?.setValue(null); } } else { this.filteredPlatforms = [...this.platforms]; } }) ?? null; this.platformControlSubscription = platformControl?.valueChanges.subscribe((platformId) => { if (platformId != null && platformId !== '') { const platformObj = this.platforms.find(p => String(p.id) === String(platformId)); const pBrandId = this.getPlatformBrandId(platformObj); if (pBrandId != null) { const pBrandIdStr = String(pBrandId); this.filteredBrands = this.brands.filter(b => String(b.id) === pBrandIdStr); const curBrandId = brandControl?.value; if (curBrandId != null && String(curBrandId) !== pBrandIdStr) { brandControl?.setValue(null); } } else { this.filteredBrands = [...this.brands]; } } else { this.filteredBrands = [...this.brands]; } }) ?? null; } ngOnDestroy(): void { this.addProductSubscription?.unsubscribe(); this.brandControlSubscription?.unsubscribe(); this.platformControlSubscription?.unsubscribe(); this.brandSubscription?.unsubscribe(); 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) return; this.isLoading = true; const raw = this.addProductForm.value; // 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 { const field = this.addProductForm.get(fieldName); return Boolean(field && field.invalid && (field.dirty || field.touched || this.isSubmitted)); } getFieldError(fieldName: string): string { const field = this.addProductForm.get(fieldName); if (field && field.errors) { if (field.errors['required']) return `Ce champ est obligatoire`; if (field.errors['email']) return `Format d'email invalide`; if (field.errors['minlength']) return `Minimum ${field.errors['minlength'].requiredLength} caractères`; if (field.errors['maxlength']) return `Maximum ${field.errors['maxlength'].requiredLength} caractères`; } return ''; } compareById = (a: any, b: any) => { if (a == null || b == null) return a === b; if (typeof a !== 'object' || typeof b !== 'object') { return String(a) === String(b); } return String(a.id ?? a) === String(b.id ?? b); }; }