CUB-43: Add inventory dashboard summary component
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m19s
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m19s
This commit is contained in:
@@ -1,176 +1,52 @@
|
||||
import { Injectable, signal, computed } from '@angular/core';
|
||||
import { Filament, classifyStockLevel } from '../models/filament.model';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { signal } from '@angular/core';
|
||||
import { Filament } 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.
|
||||
* API base URL — matches the Extrudex backend.
|
||||
* TODO: Move to environment config when environments are set up.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
const API_BASE_URL = '/api/filaments';
|
||||
|
||||
/**
|
||||
* Service for managing filament inventory data.
|
||||
*
|
||||
* Provides:
|
||||
* - A reactive `filaments` signal for components to bind to
|
||||
* - REST methods for GET, POST, DELETE endpoints
|
||||
* - Real-time updates via SignalR should be layered on top when the hub is ready
|
||||
*/
|
||||
@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: '',
|
||||
},
|
||||
]);
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
/** Public read-only view of all filaments */
|
||||
readonly filaments = this._filaments.asReadonly();
|
||||
/** Reactive filament data — components read from this signal */
|
||||
readonly filaments = signal<Filament[]>([]);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/** Fetch all filament spools and update the signal */
|
||||
getFilaments(): Observable<Filament[]> {
|
||||
const req = this.http.get<Filament[]>(API_BASE_URL);
|
||||
req.subscribe({
|
||||
next: (data) => this.filaments.set(data),
|
||||
error: (err) => console.error('Failed to load filaments:', err),
|
||||
});
|
||||
return req;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
/** Fetch a single filament by ID */
|
||||
getFilament(id: string): Observable<Filament> {
|
||||
return this.http.get<Filament>(`${API_BASE_URL}/${id}`);
|
||||
}
|
||||
|
||||
/** Computed: count of active spools */
|
||||
readonly activeCount = computed(() =>
|
||||
this._filaments().filter((f) => f.isActive).length
|
||||
);
|
||||
/** Set filament data directly — used by components or SignalR handlers */
|
||||
setFilaments(data: Filament[]): void {
|
||||
this.filaments.set(data);
|
||||
}
|
||||
|
||||
/** 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
|
||||
);
|
||||
}
|
||||
/** Delete a filament spool by ID */
|
||||
deleteFilament(id: string): Observable<void> {
|
||||
return this.http.delete<void>(`${API_BASE_URL}/${id}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user