-
-
+
+
@if (criticalCount() > 0) {
@@ -26,7 +22,7 @@
-
+
+ @if (filteredFilaments().length === 0 && filaments().length > 0) {
+
+
filter_alt_off
+
No filaments match the current filters
+
+ }
+
+
@if (filaments().length === 0) {
inventory_2
diff --git a/frontend/src/app/components/filament-table/filament-table.component.ts b/frontend/src/app/components/filament-table/filament-table.component.ts
index a9f427c..f20b831 100644
--- a/frontend/src/app/components/filament-table/filament-table.component.ts
+++ b/frontend/src/app/components/filament-table/filament-table.component.ts
@@ -3,7 +3,6 @@ import {
Component,
Input,
computed,
- inject,
signal,
} from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -13,18 +12,13 @@ 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 { FilamentFilterComponent, FilamentFilterState } from '../filament-filter/filament-filter.component';
import {
Filament,
StockLevel,
getRemainingPercent,
classifyStockLevel,
} from '../../models/filament.model';
-import {
- FilamentDialogComponent,
- FilamentDialogData,
-} from '../filament-dialog/filament-dialog.component';
/** Display column definitions for the filament table */
export type FilamentColumn =
@@ -34,8 +28,7 @@ export type FilamentColumn =
| 'serial'
| 'remaining'
| 'stockLevel'
- | 'status'
- | 'actions';
+ | 'status';
@Component({
selector: 'app-filament-table',
@@ -48,16 +41,13 @@ export type FilamentColumn =
MatProgressBarModule,
MatTooltipModule,
MatSortModule,
- MatButtonModule,
- MatDialogModule,
+ FilamentFilterComponent,
],
templateUrl: './filament-table.component.html',
styleUrl: './filament-table.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FilamentTableComponent {
- private readonly dialog = inject(MatDialog);
-
/** Filament data input — reactive signal for live updates */
readonly filaments = signal([]);
@@ -77,15 +67,29 @@ export class FilamentTableComponent {
'remaining',
'stockLevel',
'status',
- 'actions',
]);
/** Default columns for template binding */
readonly columns = this._displayedColumns;
+ /** Current filter state */
+ readonly filterState = signal({
+ materialBaseNames: [],
+ colorSearch: '',
+ lowStockOnly: false,
+ activeOnly: false,
+ });
+
/** Sorted filament data */
readonly sortedFilaments = signal([]);
+ /** Computed: filtered + sorted filament data for display */
+ readonly filteredFilaments = computed(() => {
+ const data = this.sortedFilaments();
+ const filters = this.filterState();
+ return data.filter((f) => this.matchesFilter(f, filters));
+ });
+
/** Computed: count of low/critical spools */
readonly lowStockCount = computed(() =>
this.filaments().filter(
@@ -224,6 +228,9 @@ export class FilamentTableComponent {
this.sortedFilaments.set([...data]);
}
+ /** All filament data — for the filter component to derive material options */
+ readonly allFilaments = this.filaments;
+
/** Handle sort changes from MatSort */
sortData(sort: Sort): void {
const data = [...this.filaments()];
@@ -265,6 +272,46 @@ export class FilamentTableComponent {
this.sortedFilaments.set(sorted);
}
+ /** Handle filter changes from FilamentFilterComponent */
+ onFilterChange(state: FilamentFilterState): void {
+ this.filterState.set(state);
+ }
+
+ /** Check if a filament matches the current filter state */
+ private matchesFilter(filament: Filament, filters: FilamentFilterState): boolean {
+ // Material filter — empty means all
+ if (
+ filters.materialBaseNames.length > 0 &&
+ !filters.materialBaseNames.includes(filament.materialBaseName)
+ ) {
+ return false;
+ }
+
+ // Color search — empty means all
+ if (
+ filters.colorSearch &&
+ !filament.colorName.toLowerCase().includes(filters.colorSearch) &&
+ !filament.colorHex.toLowerCase().includes(filters.colorSearch)
+ ) {
+ return false;
+ }
+
+ // Low stock filter — show only critical/low
+ if (filters.lowStockOnly) {
+ const level = classifyStockLevel(filament);
+ if (level !== 'critical' && level !== 'low') {
+ return false;
+ }
+ }
+
+ // Active only filter
+ if (filters.activeOnly && !filament.isActive) {
+ return false;
+ }
+
+ return true;
+ }
+
/** Template helper: get remaining percent */
getRemainingPercent = getRemainingPercent;
@@ -306,47 +353,6 @@ export class FilamentTableComponent {
}
return `${Math.round(grams)}g`;
}
-
- /** Open the add filament dialog. */
- openAddDialog(): void {
- const data: FilamentDialogData = {};
- const ref = this.dialog.open(FilamentDialogComponent, {
- width: '600px',
- maxWidth: '95vw',
- data,
- autoFocus: 'first-typable',
- });
-
- ref.afterClosed().subscribe((result: boolean) => {
- if (result) {
- this.onFilamentSaved();
- }
- });
- }
-
- /** Open the edit filament dialog for a specific spool. */
- openEditDialog(filament: Filament): void {
- const data: FilamentDialogData = { filament };
- const ref = this.dialog.open(FilamentDialogComponent, {
- width: '600px',
- maxWidth: '95vw',
- data,
- autoFocus: 'first-typable',
- });
-
- ref.afterClosed().subscribe((result: boolean) => {
- if (result) {
- this.onFilamentSaved();
- }
- });
- }
-
- /** Called after a successful save — reload filament data. */
- protected onFilamentSaved(): void {
- // TODO: Replace with FilamentService.refresh() call when SignalR integration is ready.
- // For now, this is the hook for refreshing data after a save.
- // Consumers can override or listen to signal changes.
- }
}
/** Compare helper for sorting */