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

11 KiB
Raw Permalink Blame History

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

<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

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:

<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