177 lines
4.9 KiB
TypeScript
177 lines
4.9 KiB
TypeScript
|
|
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<Filament[]>([
|
||
|
|
{
|
||
|
|
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
|
||
|
|
);
|
||
|
|
}
|