docs(architecture): add festival hierarchy UX specification
Captures the approved UX specification for the festival hierarchy: four dropdown scenarios (standard sub-event, cross_event section, flat event, location-based), context-preservation on navigation, and info-tooltip placement rules. This document has been referenced in implementation work since April but was never committed.
This commit is contained in:
322
dev-docs/UX_SPEC_FESTIVAL_HIERARCHY.md
Normal file
322
dev-docs/UX_SPEC_FESTIVAL_HIERARCHY.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# UX-SPEC: Festival hiërarchie in de GUI
|
||||
|
||||
> **Status:** Definitief ontwerp — goedgekeurd door product owner
|
||||
> **Datum:** April 2026
|
||||
> **Doel:** Specificatie voor de weergave van tijdsloten, secties en shifts
|
||||
> binnen de festival/sub-event hiërarchie in `apps/app/`
|
||||
|
||||
---
|
||||
|
||||
## 1. Ontwerpprincipes
|
||||
|
||||
### 1.1 Gebruik de namen van de coördinator, niet de onze
|
||||
Groepslabels in dropdowns en lijsten zijn altijd de **daadwerkelijke namen**
|
||||
van het festival en de programmaonderdelen (sub-events). Nooit technisch
|
||||
jargon als "overkoepelend", "cross_event", "parent", of "festival-level".
|
||||
|
||||
De coördinator heeft deze namen zelf ingesteld en herkent ze altijd —
|
||||
ongeacht of sub-events dagen, locaties, tracks of edities vertegenwoordigen.
|
||||
|
||||
### 1.2 Context-behoud boven technische zuiverheid
|
||||
Als een coördinator vanuit sub-event "Vrijdag" op sectie "EHBO" klikt,
|
||||
blijft de UI in de Vrijdag-context. De coördinator ziet EHBO-diensten
|
||||
relevant voor Vrijdag, met een link om uit te zoomen naar het totaaloverzicht.
|
||||
|
||||
### 1.3 Progressieve onthulling via info-tooltips
|
||||
De interface is standaard schoon. Op drie vaste plekken staan info-iconen
|
||||
(ⓘ) met contextuele uitleg en concrete voorbeelden. Wie het concept snapt,
|
||||
klikt er nooit op. Wie het niet snapt, leest één keer en begrijpt het.
|
||||
|
||||
### 1.4 Eén patroon, overal herkenbaar
|
||||
Het visuele patroon is identiek op elk scherm: eigen items bovenaan
|
||||
(prominent), festival-items eronder (iets gedimpt, met festivalnaam als
|
||||
groepslabel). Eén patroon leren = overal begrijpen.
|
||||
|
||||
### 1.5 Flat events = geen complexiteit tonen
|
||||
Bij een enkelvoudig evenement (geen sub-events) worden alle groepslabels,
|
||||
info-tooltips en hiërarchie-elementen weggelaten. De dropdown is een platte
|
||||
lijst. Geen uitleg nodig — het spreekt voor zich.
|
||||
|
||||
---
|
||||
|
||||
## 2. Vier dropdown-scenario's
|
||||
|
||||
### 2.1 Scenario A — Standaard sectie op een sub-event (meest voorkomend)
|
||||
|
||||
**Context:** Bijv. "Bar Schirmbar" binnen "Dag 1 — Vrijdag" van "Echt Feesten 2026"
|
||||
|
||||
**Dropdown-inhoud:**
|
||||
1. Groepslabel: **[sub-event naam]** (bijv. "Dag 1 — Vrijdag")
|
||||
2. Tijdsloten van het sub-event — normale opacity, gesorteerd op start_time
|
||||
3. Groepslabel: **[festival naam]** (bijv. "Echt Feesten 2026")
|
||||
4. Tijdsloten van het festival — **opacity: 0.65**, gesorteerd op datum + start_time
|
||||
|
||||
**API-call:** `GET /events/{sub_event_id}/time-slots?include_parent=true`
|
||||
|
||||
**Info-tooltip tekst:**
|
||||
> Kies het tijdvenster voor deze dienst. Je ziet de tijdsloten van
|
||||
> **[sub-event naam]** en de algemene tijdsloten van **[festival naam]**.
|
||||
>
|
||||
> **Tip:** voor een reguliere dienst kies je een tijdslot van [sub-event naam].
|
||||
> Voor een opbouw- of afbraakdienst kies je een festivaltijdslot.
|
||||
|
||||
### 2.2 Scenario B — Festival-brede sectie (cross_event)
|
||||
|
||||
**Context:** Bijv. "EHBO" (type=cross_event) bekeken vanuit "Dag 1 — Vrijdag"
|
||||
|
||||
**Dropdown-inhoud:**
|
||||
1. Groepslabel: **[festival naam]** (bijv. "Echt Feesten 2026")
|
||||
2. Festival-level tijdsloten — normale opacity
|
||||
3. Groepslabel: **[sub-event 1 naam]** (bijv. "Dag 1 — Vrijdag")
|
||||
4. Tijdsloten van sub-event 1
|
||||
5. Groepslabel: **[sub-event 2 naam]** (bijv. "Dag 2 — Zaterdag")
|
||||
6. Tijdsloten van sub-event 2
|
||||
7. *(herhaal voor alle sub-events)*
|
||||
|
||||
**API-call:** Festival-level tijdsloten via `GET /events/{festival_id}/time-slots`
|
||||
plus tijdsloten van alle sub-events. Dit vereist een nieuwe API-optie of
|
||||
een frontend-aggregatie van meerdere calls.
|
||||
|
||||
**Aandachtspunt voor implementatie:** de dropdown kan lang worden bij grote
|
||||
festivals (3 festival-slots + 4 slots × 5 sub-events = 23 items). De
|
||||
groepslabels maken het scanbaar. Overweeg bij > 20 items een zoekfilter
|
||||
in de dropdown (Vuetify `v-autocomplete` i.p.v. `v-select`).
|
||||
|
||||
**Navigatie-indicator:** toon achter de sectienaam in de breadcrumb een
|
||||
chip "festival-breed" zodat de coördinator weet dat deze sectie anders
|
||||
werkt dan standaard secties.
|
||||
|
||||
**Info-tooltip tekst:**
|
||||
> **[sectie naam]** is een festival-brede sectie — actief bij elk
|
||||
> programmaonderdeel. Je kunt tijdsloten kiezen van **[festival naam]**
|
||||
> en van alle programmaonderdelen.
|
||||
>
|
||||
> **Tip:** plan diensten per programmaonderdeel (bijv. een showavond)
|
||||
> of festival-breed (bijv. opbouw, nachtsecurity).
|
||||
|
||||
### 2.3 Scenario C — Flat event (geen sub-events)
|
||||
|
||||
**Context:** Bijv. "Braderie Dorpstown 2026" — geen hiërarchie.
|
||||
|
||||
**Dropdown-inhoud:** platte lijst, geen groepslabels, geen info-tooltip.
|
||||
|
||||
**API-call:** `GET /events/{event_id}/time-slots` (geen include_parent)
|
||||
|
||||
### 2.4 Scenario D — Locatie-/track-gebaseerde sub-events
|
||||
|
||||
**Context:** Bijv. "Cultuurnacht Rotterdam" met locaties als sub-events.
|
||||
|
||||
**Gedrag:** identiek aan scenario A. Het patroon werkt automatisch omdat de
|
||||
sub-event namen de locatienamen zijn (bijv. "Theater Walhalla"). De
|
||||
groepslabels in de dropdown tonen dan locatienamen i.p.v. dagnamen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Secties-overzicht
|
||||
|
||||
### 3.1 Op een sub-event pagina
|
||||
|
||||
Festival-brede secties (type=cross_event) tonen de **festivalnaam** als
|
||||
context achter de sectienaam. Standaard secties tonen niets extra.
|
||||
|
||||
```
|
||||
EHBO · Echt Feesten 2026 4 diensten · 12/16
|
||||
Security · Echt Feesten 2026 6 diensten · 18/24
|
||||
Bar Schirmbar 8 diensten · 20/24
|
||||
Podium crew 3 diensten · 6/9
|
||||
```
|
||||
|
||||
De festivalnaam achter de sectienaam is voldoende context. Geen badges,
|
||||
geen groepen, geen kleuren.
|
||||
|
||||
**Info-tooltip (ⓘ bij de secties-header):**
|
||||
> Sommige secties zijn **festival-breed**: ze zijn bij elk
|
||||
> programmaonderdeel actief en worden centraal beheerd. Je herkent ze
|
||||
> aan de festivalnaam achter de sectienaam.
|
||||
>
|
||||
> **Voorbeeld:** EHBO en Security zijn festival-breed — ze staan bij elk
|
||||
> programmaonderdeel. Bar Schirmbar hoort alleen bij [sub-event naam].
|
||||
|
||||
### 3.2 Op de festival-pagina
|
||||
|
||||
Alle secties getoond: standard secties (operationeel) + cross_event secties.
|
||||
Geen speciale markering nodig — alles hoort hier.
|
||||
|
||||
### 3.3 Op een flat event
|
||||
|
||||
Gewoon een platte lijst. Geen contextlabels nodig.
|
||||
|
||||
---
|
||||
|
||||
## 4. Tijdsloten-pagina
|
||||
|
||||
### 4.1 Op een sub-event pagina
|
||||
|
||||
Eén lijst, geen tabs. Boven: eigen tijdsloten (bewerkbaar, CRUD-acties).
|
||||
Onder: festival-tijdsloten, gescheiden door een stippellijn met slotje.
|
||||
|
||||
```
|
||||
[Eigen tijdsloten — bewerkbaar]
|
||||
Vrijdag avond — vrijwilliger 18:00 – 02:00 [Bewerken] [Verwijderen]
|
||||
Vrijdag avond — crew 17:00 – 03:00 [Bewerken] [Verwijderen]
|
||||
|
||||
──── 🔒 Echt Feesten 2026 — alleen-lezen ────
|
||||
|
||||
Opbouw vrijdag vr 08:00 – 18:00
|
||||
Nachtsecurity vr→za vr 23:00 – za 07:00
|
||||
|
||||
→ Beheer tijdsloten van Echt Feesten 2026
|
||||
```
|
||||
|
||||
### 4.2 Op de festival-pagina
|
||||
|
||||
Gewone CRUD-lijst met alle festival-level tijdsloten. Geen read-only
|
||||
sectie — alles is bewerkbaar op dit niveau.
|
||||
|
||||
### 4.3 Op een flat event
|
||||
|
||||
Gewone CRUD-lijst. Geen scheiding, geen read-only sectie.
|
||||
|
||||
---
|
||||
|
||||
## 5. Navigatiegedrag bij festival-brede secties
|
||||
|
||||
### 5.1 Klikken op een festival-brede sectie vanuit sub-event context
|
||||
|
||||
De coördinator blijft in de sub-event context. Bovenaan de sectie-view
|
||||
staat een context-banner:
|
||||
|
||||
```
|
||||
Je bekijkt EHBO vanuit Dag 1 — Vrijdag [Bekijk alle diensten →]
|
||||
```
|
||||
|
||||
De shift-lijst toont:
|
||||
- Diensten met een tijdslot van dit sub-event
|
||||
- Diensten met een festival-level tijdslot
|
||||
- NIET: diensten met tijdsloten van andere sub-events
|
||||
|
||||
"Bekijk alle diensten" navigeert naar de sectie op festival-niveau, waar
|
||||
alle diensten over alle programmaonderdelen zichtbaar zijn.
|
||||
|
||||
### 5.2 Festival-brede sectie op festival-niveau
|
||||
|
||||
Alle diensten, gegroepeerd per programmaonderdeel (sub-event naam) + een
|
||||
groep voor festival-level diensten. Dit is het "totaaloverzicht" voor de
|
||||
EHBO-coördinator.
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementatie-aantekeningen
|
||||
|
||||
### 6.1 Backend
|
||||
|
||||
**Bestaande API die werkt:**
|
||||
- `GET /events/{event}/time-slots?include_parent=true` — retourneert eigen +
|
||||
parent festival tijdsloten, elk met `source` veld (`own` | `festival`) en
|
||||
`event_name`
|
||||
|
||||
**Nieuwe API nodig voor scenario B (cross_event sectie):**
|
||||
- Optie 1: Frontend doet meerdere calls (festival + alle sub-events)
|
||||
- Optie 2: Nieuw endpoint `GET /events/{festival}/time-slots?include_children=true`
|
||||
dat alle tijdsloten van festival + alle sub-events retourneert
|
||||
- **Aanbeveling:** optie 2, met `source` = `own` | `{sub_event_id}` en
|
||||
`event_name` per tijdslot
|
||||
|
||||
### 6.2 Frontend — v-autocomplete configuratie
|
||||
|
||||
```vue
|
||||
<v-autocomplete
|
||||
v-model="form.time_slot_id"
|
||||
:items="groupedTimeSlots"
|
||||
item-title="label"
|
||||
item-value="id"
|
||||
label="Tijdslot"
|
||||
:loading="isLoading"
|
||||
>
|
||||
<!-- Groepslabels via Vuetify's group rendering -->
|
||||
<!-- Custom item template met tijd rechts -->
|
||||
<template #item="{ props: itemProps, item }">
|
||||
<v-list-subheader v-if="item.raw.isGroupHeader">
|
||||
{{ item.raw.groupName }}
|
||||
</v-list-subheader>
|
||||
<v-list-item v-else v-bind="itemProps">
|
||||
<template #append>
|
||||
<span class="text-caption text-medium-emphasis">
|
||||
{{ item.raw.timeRange }}
|
||||
</span>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template #selection="{ item }">
|
||||
{{ item.raw.name }} · {{ item.raw.timeRange }}
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
```
|
||||
|
||||
### 6.3 Frontend — bepalen welk scenario actief is
|
||||
|
||||
```typescript
|
||||
const dropdownScenario = computed(() => {
|
||||
const event = currentEvent.value
|
||||
const section = currentSection.value
|
||||
|
||||
// Flat event — scenario C
|
||||
if (!event?.parent_event_id && !event?.children?.length) {
|
||||
return 'flat'
|
||||
}
|
||||
|
||||
// Cross_event section — scenario B
|
||||
if (section?.type === 'cross_event') {
|
||||
return 'cross_event'
|
||||
}
|
||||
|
||||
// Standard section on sub-event — scenario A (and D)
|
||||
if (event?.parent_event_id) {
|
||||
return 'sub_event_standard'
|
||||
}
|
||||
|
||||
// Standard section on festival — festival-level, no sub-event slots
|
||||
return 'festival_standard'
|
||||
})
|
||||
```
|
||||
|
||||
### 6.4 Seeder-vereisten
|
||||
|
||||
De DevSeeder moet consistent zijn met deze hiërarchie:
|
||||
- Festival-level tijdsloten op het parent event (opbouw, afbraak, etc.)
|
||||
- Sub-event tijdsloten op elk sub-event (programma-specifiek)
|
||||
- Shifts in standard secties → tijdsloten van hun eigen event
|
||||
- Shifts in cross_event secties → tijdsloten van het parent festival
|
||||
- Geen cross-references naar andere sub-events vanuit standard secties
|
||||
|
||||
### 6.5 Info-tooltip component
|
||||
|
||||
Herbruikbaar component `InfoTooltip.vue`:
|
||||
```vue
|
||||
<template>
|
||||
<v-tooltip location="bottom" max-width="320">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<v-icon
|
||||
v-bind="tooltipProps"
|
||||
icon="tabler-info-circle"
|
||||
size="16"
|
||||
color="medium-emphasis"
|
||||
class="cursor-help"
|
||||
/>
|
||||
</template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Toekomstige verbeteringen (niet in scope)
|
||||
|
||||
- **ARCH-08:** Recurrence voor tijdsloten ("kopieer naar ander sub-event")
|
||||
- Zoekfilter in de dropdown bij > 20 tijdsloten
|
||||
- Cross_event sectie per-sub-event activatie toggle (B1 — pas bij vraag)
|
||||
- Drag-and-drop herordening van tijdsloten in de dropdown
|
||||
Reference in New Issue
Block a user