diff --git a/client/src/app/components/ps-product-crud/ps-product-crud.component.html b/client/src/app/components/ps-product-crud/ps-product-crud.component.html index c1ebf79..d1606f6 100644 --- a/client/src/app/components/ps-product-crud/ps-product-crud.component.html +++ b/client/src/app/components/ps-product-crud/ps-product-crud.component.html @@ -7,6 +7,16 @@ add Nouveau produit + @if (selection.hasValue()) { + + } + Filtrer -
- -
- -
+ @if (isLoading) { +
+ +
+ } + + + + + + + + diff --git a/client/src/app/components/ps-product-crud/ps-product-crud.component.ts b/client/src/app/components/ps-product-crud/ps-product-crud.component.ts index c435b38..9e16e99 100644 --- a/client/src/app/components/ps-product-crud/ps-product-crud.component.ts +++ b/client/src/app/components/ps-product-crud/ps-product-crud.component.ts @@ -11,10 +11,12 @@ 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 {FormBuilder, ReactiveFormsModule, FormsModule} from '@angular/forms'; import {MatDialog, MatDialogModule} from '@angular/material/dialog'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {forkJoin, finalize} from 'rxjs'; +import {SelectionModel} from '@angular/cdk/collections'; +import {MatCheckboxModule} from '@angular/material/checkbox'; import {PsItem} from '../../interfaces/ps-item'; import {ProductListItem} from '../../interfaces/product-list-item'; @@ -27,14 +29,15 @@ import {ProductDialogData, PsProductDialogComponent} from '../ps-product-dialog/ templateUrl: './ps-product-crud.component.html', styleUrls: ['./ps-product-crud.component.css'], imports: [ - CommonModule, ReactiveFormsModule, + CommonModule, ReactiveFormsModule, FormsModule, MatTable, MatColumnDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, MatHeaderCell, MatHeaderCellDef, MatCell, MatCellDef, MatNoDataRow, MatSortModule, MatPaginatorModule, MatFormField, MatLabel, MatInput, MatButton, MatIconButton, MatIcon, MatDialogModule, - MatProgressSpinnerModule + MatProgressSpinnerModule, + MatCheckboxModule ] }) export class PsProductCrudComponent implements OnInit { @@ -50,12 +53,16 @@ export class PsProductCrudComponent implements OnInit { private manMap = new Map(); private supMap = new Map(); - displayed: string[] = ['id', 'name', 'category', 'manufacturer', 'supplier', 'priceTtc', 'quantity', 'actions']; + // added 'select' column first + displayed: string[] = ['select', 'id', 'name', 'category', 'manufacturer', 'supplier', 'priceTtc', 'quantity', 'actions']; dataSource = new MatTableDataSource([]); @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; @ViewChild(MatTable) table!: MatTable; + // selection model (multiple) + selection = new SelectionModel(true, []); + filterCtrl = this.fb.control(''); isLoading = false; @@ -121,6 +128,8 @@ export class PsProductCrudComponent implements OnInit { private bindProducts(p: (ProductListItem & { priceHt?: number })[]) { const vat = 0.2; + // clear selection because objects will be new after reload + this.selection.clear(); this.dataSource.data = p.map(x => ({ ...x, categoryName: x.id_category_default ? (this.catMap.get(x.id_category_default) ?? '') : '', @@ -203,4 +212,55 @@ export class PsProductCrudComponent implements OnInit { } }); } + + // --- Selection helpers --- + + private getVisibleRows(): any[] { + const data = this.dataSource.filteredData || []; + if (!this.paginator) return data; + const start = this.paginator.pageIndex * this.paginator.pageSize; + return data.slice(start, start + this.paginator.pageSize); + } + + isAllSelected(): boolean { + const visible = this.getVisibleRows(); + return visible.length > 0 && visible.every(r => this.selection.isSelected(r)); + } + + isAnySelected(): boolean { + return this.selection.hasValue(); + } + + masterToggle(checked: boolean) { + const visible = this.getVisibleRows(); + if (checked) { + visible.forEach(r => this.selection.select(r)); + } else { + visible.forEach(r => this.selection.deselect(r)); + } + } + + toggleSelection(row: any, checked: boolean) { + if (checked) this.selection.select(row); + else this.selection.deselect(row); + } + + deleteSelected() { + if (this.isLoading) return; + const ids = this.selection.selected.map(s => s.id); + if (!ids.length) return; + if (!confirm(`Supprimer ${ids.length} produit(s) sélectionné(s) ?`)) return; + + this.isLoading = true; + const calls = ids.map((id: number) => this.ps.deleteProduct(id)); + forkJoin(calls).pipe(finalize(() => { + // nothing extra, reload will clear selection + })).subscribe({ + next: () => this.reload(), + error: (e: unknown) => { + this.isLoading = false; + alert('Erreur: ' + (e instanceof Error ? e.message : String(e))); + } + }); + } }
+ + + + + + ID {{ el.id }}