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([ { id: '1', materialBaseId: 'm1', materialBaseName: 'PLA', materialFinishId: 'f1', materialFinishName: 'Basic', materialModifierId: null, materialModifierName: null, brand: 'Bambu Lab', colorName: 'White', colorHex: '#F5F5F5', weightTotalGrams: 1000, weightRemainingGrams: 850, filamentDiameterMm: 1.75, spoolSerial: 'SN-001', purchasePrice: 25.00, purchaseDate: '2026-01-15T00:00:00Z', isActive: true, createdAt: '2026-01-15T00:00:00Z', updatedAt: '2026-04-20T00:00:00Z', qrCodeUrl: '', }, { id: '2', materialBaseId: 'm2', materialBaseName: 'PETG', materialFinishId: 'f2', materialFinishName: 'Matte', materialModifierId: 'mod1', materialModifierName: 'Carbon Fiber', brand: 'Polymaker', colorName: 'Fire Engine Red', colorHex: '#FF0000', weightTotalGrams: 1000, weightRemainingGrams: 80, filamentDiameterMm: 1.75, spoolSerial: 'SN-002', purchasePrice: 35.00, purchaseDate: '2026-02-01T00:00:00Z', isActive: true, createdAt: '2026-02-01T00:00:00Z', updatedAt: '2026-04-25T00:00:00Z', qrCodeUrl: '', }, { id: '3', materialBaseId: 'm1', materialBaseName: 'PLA', materialFinishId: 'f1', materialFinishName: 'Basic', materialModifierId: null, materialModifierName: null, brand: 'eSun', colorName: 'Sky Blue', colorHex: '#87CEEB', weightTotalGrams: 1000, weightRemainingGrams: 200, filamentDiameterMm: 1.75, spoolSerial: 'SN-003', purchasePrice: 20.00, purchaseDate: '2026-03-10T00:00:00Z', isActive: true, createdAt: '2026-03-10T00:00:00Z', updatedAt: '2026-04-26T00:00:00Z', qrCodeUrl: '', }, { id: '4', materialBaseId: 'm3', materialBaseName: 'ABS', materialFinishId: 'f1', materialFinishName: 'Basic', materialModifierId: null, materialModifierName: null, brand: 'Hatchbox', colorName: 'Black', colorHex: '#1A1A1A', weightTotalGrams: 1000, weightRemainingGrams: 450, filamentDiameterMm: 1.75, spoolSerial: 'SN-004', purchasePrice: 22.00, purchaseDate: null, isActive: true, createdAt: '2026-01-20T00:00:00Z', updatedAt: '2026-04-18T00:00:00Z', qrCodeUrl: '', }, { id: '5', materialBaseId: 'm1', materialBaseName: 'PLA', materialFinishId: 'f3', materialFinishName: 'Silk', materialModifierId: null, materialModifierName: null, brand: 'Overturn', colorName: 'Gold', colorHex: '#FFD700', weightTotalGrams: 500, weightRemainingGrams: 15, filamentDiameterMm: 1.75, spoolSerial: 'SN-005', purchasePrice: 28.00, purchaseDate: null, isActive: false, createdAt: '2025-12-01T00:00:00Z', updatedAt: '2026-04-01T00:00:00Z', qrCodeUrl: '', }, ]); /** Public read-only view of all filaments */ readonly filaments = this._filaments.asReadonly(); /** * Replace the full filament list. * Called by SignalR handlers, HTTP responses, or test setup. */ setFilaments(data: Filament[]): void { this._filaments.set(data); } /** * Update or insert a single filament by id. * Called when a real-time SignalR update arrives for one spool. */ upsertFilament(updated: Filament): void { this._filaments.update((current) => { const idx = current.findIndex((f) => f.id === updated.id); if (idx === -1) { return [...current, updated]; } const copy = [...current]; copy[idx] = updated; return copy; }); } /** * Remove a filament by id. * Called when a spool is deleted via real-time event. */ removeFilament(id: string): void { this._filaments.update((current) => current.filter((f) => f.id !== id)); } /** Computed: count of active spools */ readonly activeCount = computed(() => this._filaments().filter((f) => f.isActive).length ); /** Computed: count of low or critical stock spools */ readonly lowStockCount = computed(() => this._filaments().filter((f) => { const level = classifyStockLevel(f); return level === 'low' || level === 'critical'; }).length ); }