+
+
+
+ inventory_2
+ {{ totalFilamentCount() }}
+ Spools
+
+
+
+
+ {{ hasCriticalStock() ? 'error' : hasLowStock() ? 'warning' : 'check_circle' }}
+ {{ lowStockCount() }}
+ Low Stock
+
+
+
+
+ payments
+ {{ formatCurrency(estimatedTotalValue()) }}
+ Value
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/components/inventory-summary/inventory-summary.component.scss b/frontend/src/app/components/inventory-summary/inventory-summary.component.scss
new file mode 100644
index 0000000..65226f3
--- /dev/null
+++ b/frontend/src/app/components/inventory-summary/inventory-summary.component.scss
@@ -0,0 +1,93 @@
+/**
+ * Inventory Summary Component Styles
+ * Touch-optimized for kiosk (Raspberry Pi 5) and mobile PWA
+ * Consistent with dashboard-summary component styling
+ */
+
+// Touch-optimized sizing
+$touch-target-min: 48px;
+$kiosk-font-primary: 20px;
+$mobile-font-primary: 16px;
+$spacing-unit: 8px;
+
+// Status colors — high contrast for workshop/bright environments
+$color-healthy: #4ade70; // Green — stock OK
+$color-low: #facc15; // Amber — low stock
+$color-critical: #f87171; // Red — critical stock
+
+.inventory-summary {
+ display: flex;
+ align-items: center;
+ gap: $spacing-unit * 2;
+ padding: $spacing-unit * 2;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+
+ @media (max-width: 480px) {
+ padding: $spacing-unit;
+ gap: $spacing-unit;
+ }
+}
+
+.summary-item {
+ display: flex;
+ align-items: center;
+ gap: $spacing-unit;
+ min-height: $touch-target-min;
+ padding: $spacing-unit $spacing-unit * 2;
+ border-radius: 12px;
+ background-color: rgba(255, 255, 255, 0.05);
+ white-space: nowrap;
+ transition: background-color 0.3s ease;
+
+ @media (max-width: 480px) {
+ padding: $spacing-unit;
+ border-radius: 8px;
+ }
+
+ mat-icon {
+ font-size: 22px !important;
+ width: 22px !important;
+ height: 22px !important;
+ }
+
+ .metric-value {
+ font-size: $kiosk-font-primary;
+ font-weight: 600;
+ line-height: 1.2;
+
+ @media (max-width: 480px) {
+ font-size: $mobile-font-primary;
+ }
+ }
+
+ .metric-label {
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.7);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+
+ @media (max-width: 480px) {
+ font-size: 10px;
+ }
+ }
+}
+
+// Low stock states
+.low-stock {
+ &.has-alerts {
+ background-color: rgba($color-low, 0.15);
+
+ mat-icon {
+ color: $color-low;
+ }
+ }
+
+ &.has-critical {
+ background-color: rgba($color-critical, 0.15);
+
+ mat-icon {
+ color: $color-critical;
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/inventory-summary/inventory-summary.component.ts b/frontend/src/app/components/inventory-summary/inventory-summary.component.ts
new file mode 100644
index 0000000..9ada5ec
--- /dev/null
+++ b/frontend/src/app/components/inventory-summary/inventory-summary.component.ts
@@ -0,0 +1,81 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ inject,
+} from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { FilamentService } from '../../services/filament.service';
+import { classifyStockLevel } from '../../models/filament.model';
+
+/**
+ * InventorySummaryComponent — dashboard summary for filament inventory metrics.
+ *
+ * Displays three key metrics driven by FilamentService:
+ * - Total filament count (active spools)
+ * - Low stock count (spools classified as 'low' or 'critical')
+ * - Estimated total filament value (sum of purchase prices for active spools)
+ *
+ * All values update dynamically whenever FilamentService data changes.
+ */
+@Component({
+ selector: 'app-inventory-summary',
+ standalone: true,
+ imports: [
+ CommonModule,
+ MatIconModule,
+ MatTooltipModule,
+ ],
+ templateUrl: './inventory-summary.component.html',
+ styleUrl: './inventory-summary.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class InventorySummaryComponent {
+ private readonly filamentService = inject(FilamentService);
+
+ /** Computed: total number of active filament spools */
+ readonly totalFilamentCount = computed(() =>
+ this.filamentService.filaments().filter((f) => f.isActive).length
+ );
+
+ /** Computed: count of spools at low or critical stock levels */
+ readonly lowStockCount = computed(() =>
+ this.filamentService.filaments().filter((f) => {
+ const level = classifyStockLevel(f);
+ return level === 'low' || level === 'critical';
+ }).length
+ );
+
+ /** Computed: count of spools at critical stock level only */
+ readonly criticalStockCount = computed(() =>
+ this.filamentService.filaments().filter(
+ (f) => classifyStockLevel(f) === 'critical'
+ ).length
+ );
+
+ /** Computed: estimated total value of all active spools with a recorded price */
+ readonly estimatedTotalValue = computed(() =>
+ this.filamentService
+ .filaments()
+ .filter((f) => f.isActive && f.purchasePrice !== null)
+ .reduce((sum, f) => sum + (f.purchasePrice ?? 0), 0)
+ );
+
+ /** Computed: whether there are low-stock spools to highlight */
+ readonly hasLowStock = computed(() => this.lowStockCount() > 0);
+
+ /** Computed: whether there are critical-stock spools */
+ readonly hasCriticalStock = computed(() => this.criticalStockCount() > 0);
+
+ /** Format a currency value for display */
+ formatCurrency(value: number): string {
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 2,
+ }).format(value);
+ }
+}
diff --git a/frontend/src/app/services/filament.service.ts b/frontend/src/app/services/filament.service.ts
new file mode 100644
index 0000000..417b361
--- /dev/null
+++ b/frontend/src/app/services/filament.service.ts
@@ -0,0 +1,176 @@
+import { Injectable, signal, computed } from '@angular/core';
+import { Filament, classifyStockLevel } from '../models/filament.model';
+
+/**
+ * FilamentService — shared reactive state for filament inventory.
+ *
+ * Provides a single source of truth for filament data across components.
+ * Both FilamentTableComponent and InventorySummaryComponent read from this service.
+ * Data is updated via setFilaments() — called by SignalR handlers or HTTP load.
+ */
+@Injectable({
+ providedIn: 'root',
+})
+export class FilamentService {
+ /** Primary data store — reactive signal */
+ private readonly _filaments = signal