204 lines
6.4 KiB
TypeScript
204 lines
6.4 KiB
TypeScript
import {Component, computed, inject, OnInit, output, signal, viewChild} from '@angular/core';
|
|
import {NzTableComponent, NzThMeasureDirective} from "ng-zorro-antd/table";
|
|
import {ModalNav} from "../modal-nav/modal-nav";
|
|
import {NzIconDirective} from "ng-zorro-antd/icon";
|
|
import {StockForm} from "../stock-form/stock-form";
|
|
import {NzDividerComponent} from "ng-zorro-antd/divider";
|
|
import {FormsModule} from "@angular/forms";
|
|
import {NzCheckboxComponent} from "ng-zorro-antd/checkbox";
|
|
import {GetProductDto, ProductsService, WarehouseproductsService} from "../../services/api";
|
|
import {NzNotificationService} from "ng-zorro-antd/notification";
|
|
import {firstValueFrom} from "rxjs";
|
|
|
|
interface ProductWithQuantity extends GetProductDto {
|
|
totalQuantity?: number;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-stock-table',
|
|
imports: [
|
|
NzTableComponent,
|
|
ModalNav,
|
|
NzIconDirective,
|
|
StockForm,
|
|
NzDividerComponent,
|
|
FormsModule,
|
|
NzCheckboxComponent,
|
|
NzThMeasureDirective,
|
|
],
|
|
templateUrl: './stock-table.html',
|
|
styleUrl: './stock-table.css',
|
|
})
|
|
|
|
export class StockTable implements OnInit {
|
|
private productsService = inject(ProductsService);
|
|
private wareHousseProductsService = inject(WarehouseproductsService)
|
|
private notificationService = inject(NzNotificationService)
|
|
products = signal<ProductWithQuantity[]>([]);
|
|
productsLoading = signal<boolean>(false);
|
|
updateProduct = viewChild.required<StockForm>('stockForm');
|
|
modal = viewChild.required<ModalNav>('modalNav');
|
|
|
|
checked = false;
|
|
indeterminate = false;
|
|
setOfCheckedId = new Set<number>();
|
|
selectionChange = output<boolean>()
|
|
currentPageData: GetProductDto[] = [];
|
|
|
|
private searchQuery = signal<string>('');
|
|
|
|
filteredProducts = computed(() => {
|
|
const q = this.searchQuery().toLowerCase().trim();
|
|
|
|
if (!q) return this.products();
|
|
|
|
return this.products().filter(s => {
|
|
const name = (s.name ?? '').toLowerCase();
|
|
return name.includes(q);
|
|
});
|
|
});
|
|
|
|
applySearch(query: string) {
|
|
this.searchQuery.set(query);
|
|
}
|
|
get hasSelection(): boolean {
|
|
return this.setOfCheckedId.size > 0;
|
|
}
|
|
|
|
updateCheckedSet(id: number, checked: boolean): void {
|
|
if (checked) this.setOfCheckedId.add(id);
|
|
else this.setOfCheckedId.delete(id);
|
|
}
|
|
|
|
onItemChecked(id: number, checked: boolean): void {
|
|
this.updateCheckedSet(id, checked);
|
|
this.refreshCheckedStatus();
|
|
}
|
|
|
|
onAllChecked(checked: boolean): void {
|
|
this.currentPageData.forEach(item =>
|
|
this.updateCheckedSet(item.id, checked)
|
|
);
|
|
this.refreshCheckedStatus();
|
|
}
|
|
|
|
onCurrentPageDataChange($event: GetProductDto[]): void {
|
|
this.currentPageData = $event;
|
|
this.refreshCheckedStatus();
|
|
}
|
|
|
|
refreshCheckedStatus(): void {
|
|
const total = this.currentPageData.length;
|
|
const checkedCount = this.currentPageData.filter(p => this.setOfCheckedId.has(p.id)).length;
|
|
|
|
this.checked = checkedCount === total;
|
|
this.indeterminate = checkedCount > 0 && checkedCount < total;
|
|
|
|
setTimeout(() => this.selectionChange.emit(this.hasSelection));
|
|
}
|
|
|
|
get selectedIds() {
|
|
return Array.from(this.setOfCheckedId);
|
|
}
|
|
|
|
async ngOnInit() {
|
|
await this.fetchProducts();
|
|
}
|
|
|
|
async fetchTotalQuantity(product: ProductWithQuantity) {
|
|
try {
|
|
const res = await firstValueFrom(
|
|
this.wareHousseProductsService.getTotalQuantityEndpoint(product.id)
|
|
);
|
|
product.totalQuantity = res.totalQuantity;
|
|
} catch (e) {
|
|
product.totalQuantity = 0;
|
|
}
|
|
}
|
|
|
|
async fetchProducts() {
|
|
this.productsLoading.set(true);
|
|
try {
|
|
const products = await firstValueFrom(this.productsService.getAllProductsEndpoint());
|
|
|
|
// transforme chaque produit en ProductWithQuantity
|
|
const productsWithQuantity: ProductWithQuantity[] = products.map(p => ({ ...p }));
|
|
|
|
this.products.set(productsWithQuantity);
|
|
|
|
// récupérer la quantité pour chaque produit en parallèle
|
|
await Promise.all(productsWithQuantity.map(p => this.fetchTotalQuantity(p)));
|
|
|
|
// déclencher la mise à jour du signal
|
|
this.products.set([...productsWithQuantity]);
|
|
} catch (e) {
|
|
this.notificationService.error(
|
|
'Erreur',
|
|
'Erreur de communication avec l\'API'
|
|
);
|
|
}
|
|
this.productsLoading.set(false);
|
|
}
|
|
|
|
async delete(productId:number) {
|
|
try {
|
|
await firstValueFrom(this.productsService.deleteProductEndpoint(productId))
|
|
this.notificationService.success(
|
|
'Success',
|
|
'Suppression effectuée'
|
|
)
|
|
} catch (e) {
|
|
this.notificationService.error(
|
|
'Erreur',
|
|
'Impossible de supprimer la ligne'
|
|
)
|
|
}
|
|
await this.fetchProducts();
|
|
}
|
|
|
|
async edit(id: number, updateProductComponent: StockForm) {
|
|
if (updateProductComponent.stockForm.invalid) {
|
|
this.notificationService.error(
|
|
'Erreur',
|
|
'Erreur d\'écriture dans le formulaire'
|
|
)
|
|
return;
|
|
}
|
|
|
|
try {
|
|
|
|
const products = updateProductComponent.stockForm.getRawValue();
|
|
await firstValueFrom(this.productsService.patchProductMinimalStockEndpoint(id, products))
|
|
|
|
this.notificationService.success(
|
|
'Success',
|
|
'Limite de stock modifiée'
|
|
)
|
|
} catch (e) {
|
|
console.error(e);
|
|
this.notificationService.error(
|
|
'Erreur',
|
|
'Erreur lors de la modification'
|
|
)
|
|
}
|
|
}
|
|
|
|
selectedProduct: GetProductDto | null = null;
|
|
openEditModal(product: GetProductDto) {
|
|
this.selectedProduct = { ...product};
|
|
this.modal().showModal();
|
|
}
|
|
|
|
async onModalOk(productId: number, updateProductComponent: StockForm, modal: ModalNav) {
|
|
if (!this.selectedProduct) return;
|
|
|
|
await this.edit(productId, updateProductComponent);
|
|
updateProductComponent.stockForm.reset();
|
|
modal.isVisible = false;
|
|
await this.fetchProducts();
|
|
}
|
|
|
|
onModalCancel(modal: ModalNav) {
|
|
modal.isVisible = false;
|
|
}
|
|
} |