CUB-36: add delete confirmation dialog for filament spool removal
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<!-- Filament Inventory Table — with low stock indicators -->
|
||||
<!-- Filament Inventory Table — with low stock indicators and delete actions -->
|
||||
<div class="filament-table-container" role="region" aria-label="Filament inventory">
|
||||
|
||||
<!-- Low Stock Alert Banner — shown when critical or low stock spools exist -->
|
||||
@@ -106,10 +106,32 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column — delete button -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||
<td mat-cell *matCellDef="let filament">
|
||||
<button mat-icon-button
|
||||
type="button"
|
||||
color="warn"
|
||||
[attr.aria-label]="'Delete ' + filament.materialBaseName + ' — ' + filament.colorName"
|
||||
matTooltip="Delete spool"
|
||||
matTooltipPosition="above"
|
||||
[disabled]="deleting() === filament.id"
|
||||
(click)="onDeleteClick(filament)">
|
||||
@if (deleting() === filament.id) {
|
||||
<mat-icon aria-hidden="true">hourglass_empty</mat-icon>
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">delete</mat-icon>
|
||||
}
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns()"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: columns();"
|
||||
[class.row-critical]="classifyStockLevel(row) === 'critical'"
|
||||
[class.row-low]="classifyStockLevel(row) === 'low'">
|
||||
[class.row-low]="classifyStockLevel(row) === 'low'"
|
||||
[class.row-deleting]="deleting() === row.id">
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -235,6 +235,20 @@ mat-chip {
|
||||
}
|
||||
}
|
||||
|
||||
// Actions column
|
||||
.actions-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// Row being deleted — subtle fade
|
||||
:host ::ng-deep .row-deleting {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
// Empty state
|
||||
.empty-state {
|
||||
display: flex;
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Component,
|
||||
Input,
|
||||
computed,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
@@ -12,12 +13,21 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
|
||||
import {
|
||||
Filament,
|
||||
StockLevel,
|
||||
getRemainingPercent,
|
||||
classifyStockLevel,
|
||||
} from '../../models/filament.model';
|
||||
import { FilamentService } from '../../services/filament.service';
|
||||
import {
|
||||
DeleteFilamentDialogComponent,
|
||||
DeleteFilamentDialogData,
|
||||
} from '../delete-filament-dialog/delete-filament-dialog.component';
|
||||
|
||||
/** Display column definitions for the filament table */
|
||||
export type FilamentColumn =
|
||||
@@ -27,7 +37,8 @@ export type FilamentColumn =
|
||||
| 'serial'
|
||||
| 'remaining'
|
||||
| 'stockLevel'
|
||||
| 'status';
|
||||
| 'status'
|
||||
| 'actions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-filament-table',
|
||||
@@ -40,16 +51,26 @@ export type FilamentColumn =
|
||||
MatProgressBarModule,
|
||||
MatTooltipModule,
|
||||
MatSortModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
MatSnackBarModule,
|
||||
],
|
||||
templateUrl: './filament-table.component.html',
|
||||
styleUrl: './filament-table.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FilamentTableComponent {
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private readonly filamentService = inject(FilamentService);
|
||||
|
||||
/** Filament data input — reactive signal for live updates */
|
||||
readonly filaments = signal<Filament[]>([]);
|
||||
|
||||
/** Columns to display — defaults to all columns */
|
||||
/** Whether a delete operation is in progress */
|
||||
readonly deleting = signal<string | null>(null);
|
||||
|
||||
/** Columns to display — defaults to all columns including actions */
|
||||
@Input()
|
||||
set displayedColumns(cols: FilamentColumn[]) {
|
||||
this._displayedColumns.set(cols);
|
||||
@@ -65,6 +86,7 @@ export class FilamentTableComponent {
|
||||
'remaining',
|
||||
'stockLevel',
|
||||
'status',
|
||||
'actions',
|
||||
]);
|
||||
|
||||
/** Default columns for template binding */
|
||||
@@ -252,6 +274,52 @@ export class FilamentTableComponent {
|
||||
this.sortedFilaments.set(sorted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the delete confirmation dialog for a filament spool.
|
||||
* On confirm: calls DELETE endpoint and removes the row on success.
|
||||
* On cancel: dialog dismissed, no action taken.
|
||||
*/
|
||||
onDeleteClick(filament: Filament): void {
|
||||
const dialogData: DeleteFilamentDialogData = { filament };
|
||||
const dialogRef = this.dialog.open(DeleteFilamentDialogComponent, {
|
||||
data: dialogData,
|
||||
width: '480px',
|
||||
disableClose: true,
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((confirmed: boolean | undefined) => {
|
||||
if (!confirmed) {
|
||||
return; // User cancelled — no action
|
||||
}
|
||||
|
||||
// Mark as deleting for UI feedback
|
||||
this.deleting.set(filament.id);
|
||||
|
||||
this.filamentService.deleteFilament(filament.id).subscribe({
|
||||
next: () => {
|
||||
// Remove the deleted filament from local data
|
||||
const updated = this.filaments().filter((f) => f.id !== filament.id);
|
||||
this.updateFilaments(updated);
|
||||
this.deleting.set(null);
|
||||
|
||||
this.snackBar.open(
|
||||
`Deleted ${filament.materialBaseName} — ${filament.colorName}`,
|
||||
'Dismiss',
|
||||
{ duration: 4000 }
|
||||
);
|
||||
},
|
||||
error: () => {
|
||||
this.deleting.set(null);
|
||||
this.snackBar.open(
|
||||
`Failed to delete ${filament.materialBaseName} — ${filament.colorName}. Please try again.`,
|
||||
'Dismiss',
|
||||
{ duration: 6000 }
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Template helper: get remaining percent */
|
||||
getRemainingPercent = getRemainingPercent;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user