Files
crewli/dev-docs/UX_SPEC_FESTIVAL_HIERARCHY.md
bert.hausmans e552eebb85 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.
2026-04-16 22:21:22 +02:00

323 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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