import {Component, inject, OnInit, ViewChild} from '@angular/core'; import {CommonModule} from '@angular/common'; import { MatCell, MatCellDef, MatColumnDef, MatHeaderCell, MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, MatNoDataRow, MatTable, MatTableDataSource } from '@angular/material/table'; import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator'; import {MatSort, MatSortModule} from '@angular/material/sort'; import {MatFormField, MatLabel} from '@angular/material/form-field'; import {MatInput} from '@angular/material/input'; import {MatButton, MatIconButton} from '@angular/material/button'; import {MatIcon} from '@angular/material/icon'; import {FormBuilder, ReactiveFormsModule} from '@angular/forms'; import {MatDialog, MatDialogModule} from '@angular/material/dialog'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {forkJoin, finalize} from 'rxjs'; import {PsItem} from '../../interfaces/ps-item'; import {ProductListItem} from '../../interfaces/product-list-item'; import {PrestashopService} from '../../services/prestashop.serivce'; import {ProductDialogData, PsProductDialogComponent} from '../ps-product-dialog/ps-product-dialog.component'; @Component({ selector: 'app-ps-product-crud', standalone: true, templateUrl: './ps-product-crud.component.html', styleUrls: ['./ps-product-crud.component.css'], imports: [ CommonModule, ReactiveFormsModule, MatTable, MatColumnDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, MatHeaderCell, MatHeaderCellDef, MatCell, MatCellDef, MatNoDataRow, MatSortModule, MatPaginatorModule, MatFormField, MatLabel, MatInput, MatButton, MatIconButton, MatIcon, MatDialogModule, MatProgressSpinnerModule ] }) export class PsProductCrudComponent implements OnInit { private readonly fb = inject(FormBuilder); private readonly ps = inject(PrestashopService); private readonly dialog = inject(MatDialog); categories: PsItem[] = []; manufacturers: PsItem[] = []; suppliers: PsItem[] = []; private catMap = new Map(); private manMap = new Map(); private supMap = new Map(); displayed: string[] = ['id', 'name', 'category', 'manufacturer', 'supplier', 'priceTtc', 'quantity', 'actions']; dataSource = new MatTableDataSource([]); @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; @ViewChild(MatTable) table!: MatTable; filterCtrl = this.fb.control(''); isLoading = false; ngOnInit(): void { forkJoin({ cats: this.ps.list('categories'), mans: this.ps.list('manufacturers'), sups: this.ps.list('suppliers') }).subscribe({ next: ({cats, mans, sups}) => { this.categories = cats ?? []; this.catMap = new Map(this.categories.map(x => [x.id, x.name])); this.manufacturers = mans ?? []; this.manMap = new Map(this.manufacturers.map(x => [x.id, x.name])); this.suppliers = sups ?? []; this.supMap = new Map(this.suppliers.map(x => [x.id, x.name])); this.reload(); }, error: err => { console.error('Erreur lors du chargement des référentiels', err); } }); this.filterCtrl.valueChanges.subscribe(v => { this.dataSource.filter = (v ?? '').toString().trim().toLowerCase(); if (this.paginator) this.paginator.firstPage(); }); this.dataSource.filterPredicate = (row: any, f: string) => row.name?.toLowerCase().includes(f) || String(row.id).includes(f) || (row.categoryName?.toLowerCase().includes(f)) || (row.manufacturerName?.toLowerCase().includes(f)) || (row.supplierName?.toLowerCase().includes(f)) || String(row.quantity ?? '').includes(f); } private toTtc(ht: number, vat: number) { return Math.round(((ht * (1 + vat)) + Number.EPSILON) * 100) / 100; } private attachSortingAccessors() { this.dataSource.sortingDataAccessor = (item: any, property: string) => { switch (property) { case 'category': return (item.categoryName ?? '').toLowerCase(); case 'manufacturer': return (item.manufacturerName ?? '').toLowerCase(); case 'supplier': return (item.supplierName ?? '').toLowerCase(); case 'priceTtc': return Number(item.priceTtc ?? 0); case 'name': return (item.name ?? '').toLowerCase(); default: return item[property]; } }; this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; } private bindProducts(p: (ProductListItem & { priceHt?: number })[]) { const vat = 0.2; this.dataSource.data = p.map(x => ({ ...x, categoryName: x.id_category_default ? (this.catMap.get(x.id_category_default) ?? '') : '', manufacturerName: x.id_manufacturer ? (this.manMap.get(x.id_manufacturer) ?? '') : '', supplierName: x.id_supplier ? (this.supMap.get(x.id_supplier) ?? '') : '', priceTtc: this.toTtc(x.priceHt ?? 0, vat) })); this.attachSortingAccessors(); this.table?.renderRows?.(); } reload() { this.isLoading = true; this.ps.listProducts() .pipe( finalize(() => { this.isLoading = false; }) ) .subscribe({ next: p => this.bindProducts(p), error: err => { console.error('Erreur lors du chargement des produits', err); } }); } create() { if (this.isLoading) return; const data: ProductDialogData = { mode: 'create', refs: { categories: this.categories, manufacturers: this.manufacturers, suppliers: this.suppliers } }; this.dialog.open(PsProductDialogComponent, {width: '900px', data}) .afterClosed() .subscribe(ok => { if (ok) this.reload(); }); } edit(row: ProductListItem & { priceHt?: number }) { if (this.isLoading) return; const data: ProductDialogData = { mode: 'edit', productRow: row, refs: { categories: this.categories, manufacturers: this.manufacturers, suppliers: this.suppliers } }; this.dialog.open(PsProductDialogComponent, {width: '900px', data}) .afterClosed() .subscribe(ok => { if (ok) this.reload(); }); } remove(row: ProductListItem) { if (this.isLoading) return; if (!confirm(`Supprimer le produit "${row.name}" (#${row.id}) ?`)) return; this.isLoading = true; this.ps.deleteProduct(row.id) .pipe( finalize(() => { }) ) .subscribe({ next: () => this.reload(), error: (e: unknown) => { this.isLoading = false; alert('Erreur: ' + (e instanceof Error ? e.message : String(e))); } }); } }