Add selection functionality to product list with delete option
This commit is contained in:
@@ -7,6 +7,16 @@
|
||||
<mat-icon>add</mat-icon> Nouveau produit
|
||||
</button>
|
||||
|
||||
@if (selection.hasValue()) {
|
||||
<button
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
(click)="deleteSelected()"
|
||||
[disabled]="isLoading">
|
||||
<mat-icon>delete</mat-icon> Supprimer la sélection
|
||||
</button>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="filter">
|
||||
<mat-label>Filtrer</mat-label>
|
||||
<input matInput
|
||||
@@ -15,15 +25,36 @@
|
||||
[disabled]="isLoading">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="mat-elevation-z2 product-list-root">
|
||||
<!-- Overlay de chargement -->
|
||||
<div class="product-list-loading-overlay" *ngIf="isLoading">
|
||||
@if (isLoading) {
|
||||
<div class="product-list-loading-overlay">
|
||||
<mat-spinner diameter="48"></mat-spinner>
|
||||
</div>
|
||||
}
|
||||
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
|
||||
<!-- select column -->
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox
|
||||
[checked]="isAllSelected()"
|
||||
[indeterminate]="isAnySelected() && !isAllSelected()"
|
||||
(change)="masterToggle($event.checked)"
|
||||
[disabled]="isLoading">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let el">
|
||||
<mat-checkbox
|
||||
[checked]="selection.isSelected(el)"
|
||||
(change)="toggleSelection(el, $event.checked)"
|
||||
[disabled]="isLoading">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- existing columns follow (id, name, ...) -->
|
||||
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
|
||||
<td mat-cell *matCellDef="let el">{{ el.id }}</td>
|
||||
|
||||
@@ -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<number, string>();
|
||||
private supMap = new Map<number, string>();
|
||||
|
||||
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<any>([]);
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
@ViewChild(MatTable) table!: MatTable<any>;
|
||||
|
||||
// selection model (multiple)
|
||||
selection = new SelectionModel<any>(true, []);
|
||||
|
||||
filterCtrl = this.fb.control<string>('');
|
||||
|
||||
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)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user