diff --git a/client/src/app/components/ps-product-crud/ps-product-crud.component.css b/client/src/app/components/ps-product-crud/ps-product-crud.component.css index 8b91494..b0c5f8e 100644 --- a/client/src/app/components/ps-product-crud/ps-product-crud.component.css +++ b/client/src/app/components/ps-product-crud/ps-product-crud.component.css @@ -47,6 +47,21 @@ th, td { flex-shrink: 0; } +.product-list-root { + position: relative; +} + +.product-list-loading-overlay { + position: absolute; + inset: 0; + background: rgba(255, 255, 255, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + pointer-events: all; +} + mat-paginator { width: 100%; overflow: hidden; 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 1f748ce..c1ebf79 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 @@ -1,16 +1,27 @@
- Filtrer - +
-
+
+ +
+ +
+ @@ -51,8 +62,19 @@ @@ -60,10 +82,17 @@ - +
Actions - - + +
Aucune donnée. + Aucune donnée. +
- + +
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 56450b4..c435b38 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 @@ -13,7 +13,8 @@ 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 {forkJoin} from 'rxjs'; +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'; @@ -32,7 +33,8 @@ import {ProductDialogData, PsProductDialogComponent} from '../ps-product-dialog/ MatSortModule, MatPaginatorModule, MatFormField, MatLabel, MatInput, MatButton, MatIconButton, MatIcon, - MatDialogModule + MatDialogModule, + MatProgressSpinnerModule ] }) export class PsProductCrudComponent implements OnInit { @@ -40,26 +42,24 @@ export class PsProductCrudComponent implements OnInit { private readonly ps = inject(PrestashopService); private readonly dialog = inject(MatDialog); - // référentiels categories: PsItem[] = []; manufacturers: PsItem[] = []; suppliers: PsItem[] = []; - // maps d’affichage private catMap = new Map(); private manMap = new Map(); private supMap = new Map(); - // table 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; - // filtre filterCtrl = this.fb.control(''); + isLoading = false; + ngOnInit(): void { forkJoin({ cats: this.ps.list('categories'), @@ -73,6 +73,7 @@ export class PsProductCrudComponent implements OnInit { 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 => { @@ -80,7 +81,6 @@ export class PsProductCrudComponent implements OnInit { } }); - // filtre client this.filterCtrl.valueChanges.subscribe(v => { this.dataSource.filter = (v ?? '').toString().trim().toLowerCase(); if (this.paginator) this.paginator.firstPage(); @@ -133,10 +133,24 @@ export class PsProductCrudComponent implements OnInit { } reload() { - this.ps.listProducts().subscribe(p => this.bindProducts(p)); + 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: { @@ -145,12 +159,16 @@ export class PsProductCrudComponent implements OnInit { suppliers: this.suppliers } }; - this.dialog.open(PsProductDialogComponent, {width: '900px', data}).afterClosed().subscribe(ok => { - if (ok) this.reload(); - }); + 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, @@ -160,16 +178,29 @@ export class PsProductCrudComponent implements OnInit { suppliers: this.suppliers } }; - this.dialog.open(PsProductDialogComponent, {width: '900px', data}).afterClosed().subscribe(ok => { - if (ok) this.reload(); - }); + 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.ps.deleteProduct(row.id).subscribe({ - next: () => this.reload(), - error: (e: unknown) => alert('Erreur: ' + (e instanceof Error ? e.message : String(e))) - }); + + 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))); + } + }); } } diff --git a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css index e9b466d..9cfdf40 100644 --- a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css +++ b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.css @@ -163,3 +163,19 @@ .thumb-placeholder mat-icon { font-size: 28px; } + +.dialog-root { + position: relative; +} + +/* Overlay plein écran dans le dialog pendant la sauvegarde */ +.dialog-loading-overlay { + position: absolute; + inset: 0; + background: rgba(255, 255, 255, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 50; + pointer-events: all; +} diff --git a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html index dec97b9..32c2263 100644 --- a/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html +++ b/client/src/app/components/ps-product-dialog/ps-product-dialog.component.html @@ -1,162 +1,183 @@

{{ mode === 'create' ? 'Nouveau produit' : 'Modifier le produit' }}

-
+
+ + @if (isSaving) { +
+ +
+ } - -