# 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
{{ item.raw.groupName }}
{{ item.raw.timeRange }}
{{ item.raw.name }} · {{ item.raw.timeRange }}
```
### 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
```
---
## 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