207 lines
6.7 KiB
TypeScript
207 lines
6.7 KiB
TypeScript
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<number, string>();
|
|
private manMap = new Map<number, string>();
|
|
private supMap = new Map<number, string>();
|
|
|
|
displayed: string[] = ['id', 'name', 'category', 'manufacturer', 'supplier', 'priceTtc', 'quantity', 'actions'];
|
|
dataSource = new MatTableDataSource<any>([]);
|
|
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
|
@ViewChild(MatSort) sort!: MatSort;
|
|
@ViewChild(MatTable) table!: MatTable<any>;
|
|
|
|
filterCtrl = this.fb.control<string>('');
|
|
|
|
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)));
|
|
}
|
|
});
|
|
}
|
|
}
|