🍷 Wines — panoramica
La feature Wines consente a una company (Cantina/Winery) di:
- elencare e filtrare vini/annate,
- creare un Wine e una relativa Vintage,
- aggiungere una nuova Vintage a un Wine esistente,
- modificare Wine/Vintage,
- consultare il dettaglio di una Vintage.
Accesso: in Dashboard, la tab Wines è abilitata per company di tipo WINERY con ruoli adeguati (es. Admin / WineManager).
🧭 Routing e moduli
// src/dashboards/wine/wine.module.ts
@NgModule({
declarations: [WineComponent],
imports: [
SharedModule, CommonModule,
RouterModule.forChild([
{
path: '',
component: WineComponent,
children: [
{ path: '', loadChildren: () => import('./wine-home-page/wine-home-page.module').then(m => m.WineHomePageModule) },
{ path: 'create', loadChildren: () => import('./wine-create/wine-create.module').then(m => m.WineCreateModule) },
{ path: ':vintageId', loadChildren: () => import('./wine-detail-page/wine-detail-page.module').then(m => m.WineDetailPageModule) },
]
}
])
]
})
export class WineModule {}
// src/dashboards/wine/wine-detail-page/wine-detail-page.module.ts
@NgModule({
declarations: [WineDetailPageComponent],
imports: [
SharedModule, CommonModule,
RouterModule.forChild([
{ path: '', component: WineDetailPageComponent },
{ path: 'edit', loadChildren: () => import('./wine-edit-page/wine-edit-page.module').then(m => m.WineEditPageModule) },
{ path: 'create-vintage', loadChildren: () => import('./wine-add-vintage/wine-add-vintage.module').then(m => m.WineAddVintageModule) },
])
]
})
export class WineDetailPageModule {}
Mappa rotte principali
/dashboard/wines→ HomePage (lista + stats)/dashboard/wines/create→ Creazione Wine + Vintage/dashboard/wines/:vintageId→ Dettaglio Vintage/edit→ Modifica Wine/Vintage/create-vintage→ Aggiungi Vintage a Wine esistente
🏗️ Componenti e responsabilità
WineComponent (shell)
- Semplice contenitore con
<router-outlet>. - Tutta la logica vive nelle pagine figlie.
WineHomePageComponent (lista + statistiche)
- UI:
- KPI cards: #Wines, #Vintages, #eLabels.
- Search con debounce (300ms).
- Tabella con colonne:
wine(immagine + nome vintage),updatedAt(data),score(media rating), azione “apri dettaglio”. - Paginator (pagine e records per page via
AlbiOption).
- Store:
USER_ACCES_SELECTORS.selectSelectedUserAccesseseselectSelectedEnterpriseAccess→ risolvewineryId.
- Dati/Backend:
- Lista/pagine:
GET wine/wines/vintages?winery_id={...}&page={n}&limit={k}&q={search?} - Conteggi:
GET wine/wines?winery_id={...}→ #WinesGET wine/wines/vintages?winery_id={...}→ #VintagesGET elabels→ #eLabels
- Lista/pagine:
- Pipes & helpers:
resolveVintageName,resolveDateStringPipe,imageUrlWithLastUpdate.
- Navigazione:
- “Nuovo” →
/dashboard/wines/create - “Apri” (riga) →
/dashboard/wines/:vintageId
- “Nuovo” →
- Stati UI:
isLoading$,isTableDataLoading$,getErrorMessage$,selectedPage$,recordPerPageControl.
WineDetailPageComponent (dettaglio Vintage)
- Fetch:
GET wine/wines/vintages/:vintageId- poi
GET wine/wines/:wineIdcollegato.
- UI:
- Header con back.
- Box informazioni Wine (colore, tipologie, regione, ecc.) e Vintage (nome, descrizioni localizzate, immagini).
- Azioni:
- Edit →
/dashboard/wines/:vintageId/edit - Add Vintage →
/dashboard/wines/:vintageId/create-vintage
- Edit →
- Note:
- Le descrizioni vengono risolte con utilità/localizzazione (
TranslateService+ dizionari).
- Le descrizioni vengono risolte con utilità/localizzazione (
WineCreateComponent (crea Wine + Vintage)
- Form:
wineCreateForm(Wine): nome, colore, tipologie, regione/paese, uve (chips), ecc.vintageCreateForm(Vintage): anno, descrizioni, pairing, allergeni, immagini (label/bottle), ecc.
- Flusso salvataggio:
POST wine/wines→ crea Wine (status:active,wineryId).POST wine/wines/{wineId}/vintages→ crea Vintage (mapping: chips/list → array, testi, year, ecc.).- (Opzionale) Upload immagini:
POST wine/wines/{wineId}/vintages/{vintageId}/bottle(binario, headercontent-type)POST wine/wines/{wineId}/vintages/{vintageId}/label(binario, headercontent-type)
- Messaggio successo e redirect/aggiornamento.
- Rollback in caso di errore (coerenza dati):
- Se fallisce la creazione Vintage, recupera
ETagconGET (full response) wine/wines/{wineId}?raw=truee faDELETE wine/wines/{wineId}con ETag per ripulire il Wine appena creato.
- Se fallisce la creazione Vintage, recupera
WineEditPageComponent (modifica Wine/Vintage)
- Init:
GET (full response) wine/wines/vintages/:vintageId?raw=true→ETag vintageGET (full response) wine/wines/:wineId?raw=true→ETag wine- Prepopola
wineCreationFormevintageCreationForm. - Campi year e simili della Vintage sono disabilitati (immutabili).
- Salvataggio:
- PATCH con ETag (concorrenza ottimistica):
PATCH wine/wines/{wineId}(Wine)PATCH wine/wines/{wineId}/vintages/{vintageId}(Vintage)
- Upload immagini come in creazione:
POST .../labele/oPOST .../bottle(binario + headercontent-type).
- PATCH con ETag (concorrenza ottimistica):
- Mapping UI → API (esempi):
- Grapes: chips
AlbiChipType[]→ struttura richiesta dal BE. - Pairings:
primaryFoods(matchperfect) +secondaryFoods(matchgood). - Descriptions: array localizzato
{ locale, text }.
- Grapes: chips
- UI:
- Barra “Save” fissa visibile quando i form sono dirty.
WineAddVintageComponent (aggiungi Vintage a Wine esistente)
- UI:
- Mostra
wine-creation-formin read-only (Wine non editabile qui). vintage-creation-formper la nuova annata.
- Mostra
- Flusso:
POST wine/wines/{wineId}/vintagescon mapping campi (pairings, allergens, philosophy, awards, moods, descriptions, …).- (Opzionale) Upload bottle/label come in creazione.
- Messaggio successo e redirect (es. al dettaglio).
🔄 Sequenze principali (mermaid)
A) Lista & statistiche
sequenceDiagram
autonumber
participant Store as NgRx Store
participant Home as WineHomePage
participant BE as BackendService
Home->>Store: read selectedUserAccess + selectedEnterpriseAccess
Home->>Home: resolve wineryId
Home->>BE: GET /wine/wines/vintages?winery_id=&page=&limit=&q=
BE-->>Home: { vintages, pages, page }
Home->>BE: GET /wine/wines?winery_id=
BE-->>Home: { wines }
Home->>BE: GET /elabels
BE-->>Home: { elabels }
Home-->>Home: update KPI + table + paginator
B) Crea Wine + Vintage (con immagini e rollback)
flowchart TD
A[Compila Wine + Vintage] --> B[POST /wine/wines]
B -->|wineId| C[POST /wine/wines/{wineId}/vintages]
C -->|vintageId| D{Immagini presenti?}
D -- sì --> E1[POST .../{vintageId}/bottle]
E1 --> E2[POST .../{vintageId}/label]
D -- no --> F[Mostra success + redirect]
E2 --> F[Mostra success + redirect]
C -- errore --> R[GET full /wine/wines/{wineId}?raw + DELETE con ETag]
C) Edit Wine/Vintage (ETag)
sequenceDiagram
autonumber
participant Edit as WineEditPage
participant BE as BackendService
Edit->>BE: GET full /vintages/:vintageId?raw
BE-->>Edit: vintage + ETag_vintage
Edit->>BE: GET full /wines/:wineId?raw
BE-->>Edit: wine + ETag_wine
Edit-->>Edit: form patch + mapping
Edit->>BE: PATCH /wines/:wineId (If-Match: ETag_wine)
Edit->>BE: PATCH /wines/:wineId/vintages/:id (If-Match: ETag_vintage)
Edit->>BE: POST label/bottle (binari + content-type)
BE-->>Edit: 200 OK
Edit-->>Edit: success + toast + redirect
🧩 Dipendenze UI/tecniche
- albi-ui:
albi-table,albi-paginator,albi-autocomplete,albi-button,albi-icon,albi-loader, ecc. - Shared components:
wine-creation-form(gestione form del Wine)vintage-creation-form(gestione form della Vintage)
- Pipes:
resolveVintageName,resolveDateStringPipe,imageUrlWithLastUpdate. - NgRx Store:
USER_ACCES_SELECTORSper context di company/permessi. - TranslateService: etichette/titoli/dizionari (IT/EN).
- BackendService: CRUD + upload binari + ETag flow (get full response, patch, delete).
⚠️ Note operative & edge cases
- Concorrenza: in edit si usa ETag (If-Match) per evitare write conflict su Wine/Vintage.
- Rollback creazione: se fallisce la creazione della Vintage, si cancella il Wine appena creato (consistenza).
- Campi immutabili: anno/
yearTypedella Vintage non modificabili in edit (disabilitati). - Mapping UI → API:
- Chips (grapes, foods, allergens, moods, philosophy, awards) vanno normalizzati come richiesto dal BE.
- Descrizioni localizzate
{ locale, text }.
- Ricerca: input con debounce 300ms, compone
qnella query delle vintages. - UX:
- “Save bar” visibile solo quando i form sono dirty.
- Placeholder immagini e tooltip nome vintage in tabella.
- Pannello KPI sempre coerente con il wineryId attivo.
🧭 Estendere la feature
- Nuovi campi Wine/Vintage:
- estendere i FormGroup nei componenti di creazione/edit,
- aggiornare il mapping nel body di
POST/PATCH, - gestire l’eventuale upload (binari +
content-type), - aggiungere le traduzioni necessarie.
- Nuove colonne tabella: aggiornare
wineTableColumns+ template (ngSwitch) e, se serve, l’endpoint di lista (se richiede più dati). - Filtri avanzati: introdurre nuovi controlli nel HomePage e serializzare in query (
winery_id,q, range date, ecc.).