🧯 Dispenser — panoramica funzionale
La feature Dispenser consente a una company di monitorare e gestire i dispenser installati nelle proprie location (indirizzi), organizzati in stanze (rooms) e chassis (telai multi-slot).
Le principali aree sono:
- Home: selezione location, overview dispenser/chassis e alert aggregati.
- Dettaglio Dispenser: stato live (connessione, batteria, modalità), consumi, associazioni (smartbag/vintage, room), azioni (parental control, factory reset).
- Chassis (lista + dettaglio): creazione/assegnazione, ordinamento slot via drag&drop, associazione a room.
- Rooms (lista + dettaglio): gestione stanze e dispositivi associati.
La feature è caricata lazy sotto dashboard/dispenser e usa NgRx per lo stato “leggero” di contesto (location selezionata).
🧭 Routing (feature-level)
Shell con
<router-outlet>:DispenserComponent
// dispenser.module.ts (estratto concettuale)
const DISPENSER_ROUTES: Route[] = [
{ path: '', loadChildren: () => import('./dispenser-home-page/dispenser-home-page.module').then(m => m.DispenserHomePageModule) },
{ path: 'rooms', loadChildren: () => import('./rooms-component/rooms-component.module').then(m => m.RoomsComponentModule) },
{ path: 'rooms/:id', loadChildren: () => import('./room-detail/room-detail.module').then(m => m.RoomDetailModule) },
{ path: 'chassis', loadChildren: () => import('./chassis-component/chassis-component.module').then(m => m.ChassisComponentModule) },
{ path: 'chassis/:id', loadChildren: () => import('./chassis-detail/chassis-detail.module').then(m => m.ChassisDetailModule) },
{ path: ':id', loadChildren: () => import('./dispenser-detail/dispenser-detail.module').then(m => m.DispenserDetailModule) },
];
- Shared sub-module:
DispenserSharedModuleriesporta componenti condivisi (es.DispenserCardComponent).
🧠 Stato locale (NgRx) — dispenser feature
// state
export interface DispenserState {
locations: ICompanyAddress[];
selectedLocation: ICompanyAddress | null;
error?: string;
}
// actions (pagina/API)
initDispenserStore({ companyId })
changeSelectedLocation({ location })
initDispenserStoreSuccess({ locations, selectedLocation })
initDispenserStoreFailure({ error })
// selectors
selectLocations, selectSelectedLocation
// effects (estratto)
initDispenserStore:
if userAccess ha location -> success([location], location)
else -> CompanyLocationService.getCompanyAddresses(...)
selectedLocation da localStorage('dispenserSelectedLocation')
changeSelectedLocation:
persist su localStorage('dispenserSelectedLocation')
Il contesto della feature è la location (indirizzo) selezionata.
Se l’utente è vincolato a una location (nel suo access), quella viene forzata.
🏠 Home (DispenserHomePageComponent)
Stato & controlli
companyAddresses$: lista indirizzi della company.selectedAddress: FormControl<AlbiOption>: selettore location →DISPENSER_PAGE_ACTIONS.changeSelectedLocation.locationDispensers$: lista dispenser per location (arricchiti consmartbag?: ResolvedSmartbagTypeeroom?).locationChassis$: lista chassis per location (con dispenser agganciati).- Flag di caricamento:
isLocationLoadingDone$,isDispenserLoadingDone$,isChassisLoadingDone$. fullErrorList$: alert aggregati da tutti i dispenser (codici → pipe di traduzione).
UI
- Top bar: titolo, descrizione, selettore location.
- Alert board: elenco errori con link rapido al dettaglio dispenser.
- Dispenser cards (
DispenserCardComponent):- nome + icone stato connessione (
wifiFull/wifiOffline), modalità (boost/eco/silent), batteria. - immagine label della smartbag/vintage (fallback immagine dispenser base/Pro).
- click → dettaglio dispenser
dashboard/dispenser/:id.
- nome + icone stato connessione (
- Link rapidi a chassis e rooms.
Navigazione
navigate(url: string) // -> dashboard/dispenser/:id
navigateChassis(chassisId: str) // -> dashboard/dispenser/chassis/:id
🔎 Dettaglio Dispenser (DispenserDetailComponent)
Observables principali
dispenser$,smartbag$,room$,location$.chassis$,chassisRoom$(se montato su chassis + room di chassis).roomOptions$: stanze disponibili per la location.- Pannelli:
showDispenserEditPanel$,showParentalControlPanel$,showFactoryResetPanel$. pageLoading$,pageError$.
Tabelle & options
dispenserConsuptionTableColumn: colonne consumi/erogazioni.coolingModeOptions:boost | eco | silent.
Azioni tipiche
- Edit (pannello):
- form:
name,room,coolingMode, altri campi vincolati ai validator. - save → chiamate
BackendService(PATCH/PUT) + toast viaHeaderMessageService.
- form:
- Parental Control:
- abilita/disabilita blocchi operativi → endpoint dedicato.
- Factory Reset:
- dialog di conferma →
dispenserFactoryReset()(POST) → feedback UI.
- dialog di conferma →
- Navigazione: back a
dashboard/dispenser.
Errori/Alert
- Card Errors se
dispenser.alerts.length > 0con mapping testo tramiteDispenserAlertTranslatePipe(codiciW01,E0x, …).
🧱 Chassis (lista & dettaglio)
Lista (ChassisComponentComponent)
- Selettore location e opzione di creazione chassis (
showChassisCreatePanel$). roomsOptions$per assegnare rapidamente chassis a una room.- Elenco chassis per location con conteggio dispenser agganciati.
Click →dashboard/dispenser/chassis/:id.
Dettaglio (ChassisDetailComponent)
- Form
chassisUpdateGroup:name: string,totalSlots: number,room: AlbiOption.
- Gestione slot con CDK Drag&Drop:
- ordinamento dei dispenser in slot (
CdkDragDrop,CdkDragSortEvent). - salvataggio nuova disposizione → BackendService.
- ordinamento dei dispenser in slot (
- Altre azioni: riassegnazione a room, aggiornamento meta chassis.
- Back →
dashboard/dispenser/chassis.
flowchart LR
A[Load chassis] --> B[Show slot list]
B -->|drag&drop| C[Reorder slots (local)]
C --> D[Save layout -> API]
D --> E{OK?}
E -- yes --> F[Toast success]
E -- no --> G[Show error + rollback]
🚪 Rooms (lista & dettaglio)
Lista (RoomsComponentComponent)
- Selettore location.
rooms$: (room con conteggio dispenser associati).- Create room: pannello
showCreateRoomPanel$+ POST, aggiornamento lista. - Click su una room → dettaglio
dashboard/dispenser/rooms/:id.
Dettaglio (RoomDetailComponent)
roomDispensers$: dispenser nella room (arricchiti con smartbag/vintage).roomChassis$: chassis nella room (ognuno include i propri dispenser).- Azioni tipiche:
- rename room, spostamento dispenser (via UI), link ai dettagli dei dispositivi.
sequenceDiagram
autonumber
participant Room as RoomDetail
participant BE as BackendService
Room->>BE: GET room/:id (dispensers + chassis)
BE-->>Room: { roomDispensers, roomChassis }
Room-->>Room: render elenchi + azioni
Room->>BE: PATCH rename / move
BE-->>Room: 200 OK
Room-->>Room: toast + refresh
🧩 Componenti/Pipe condivisi
-
DispenserCardComponent
Presenta info sintetiche del dispenser (stato, modalità, batteria, etichetta smartbag, immagine modello).
Usato in Home e/o liste. -
DispenserAlertTranslatePipe
Mappa codici alert → messaggi human-readable (es.W01: Serving temperature out of range,E02: Cooling system error, …).
🔗 Dipendenze e integrazioni
- Store:
USER_ACCES_SELECTORS(user access & company context),DISPENSER_SELECTORS(locations & selected). - Services:
CompanyLocationService(indirizzi),BackendService(CRUD dispenser/chassis/rooms, azioni). - UI Lib (
albi-ui): icone (wifi, batteria, cooling mode), tabelle, autocomplete/select, dialog, loader, vertical tabs nel layout. - CDK DragDrop: ordinamento slot chassis.
- TranslateService: label e testi (dizionari
DASHBOARD_DICTIONARY,SHARED_DICTIONARY).
✅ Note operative per chi subentra
- Il contesto location governa ogni query: inizializzarlo con
initDispenserStoree mantenereselectedLocationcoerente (persistenza inlocalStorage). - Per aggiungere campi/azioni nel Dettaglio Dispenser:
- estendere il FormGroup e i validator,
- aggiornare il mapping nel salvataggio,
- gestire feedback via
HeaderMessageService.
- Per estendere Chassis:
- mantenere la semantica degli slot; ogni modifica d’ordine deve passare da CDK DragDrop + API dedicata.
- Rooms: trattare le room come contenitori logici; se si spostano dispenser, aggiornare entrambe le liste (room e device).
- Non bypassare la pipe degli alert: mantiene coerenza dei messaggi visibili all’utente.
flowchart TD
subgraph Context
UA[UserAccess select] -->|companyId/location| INIT[initDispenserStore]
INIT -->|locations + selected| SEL[selectedLocation]
end
subgraph Home
SEL --> H[HomePage load]
H --> DList[Dispensers by location]
H --> CList[Chassis by location]
H --> Alerts[Aggregate alerts]
end
DList --> DDet[:id -> Dispenser Detail]
CList --> CDet[chassis/:id -> Chassis Detail]
H --> Rooms[rooms -> Rooms List] --> RDet[rooms/:id -> Room Detail]