docs: design-document v1.8, dev-docs restructure, VitePress user docs scaffold, backlog update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -45,3 +45,7 @@ coverage/
|
||||
|
||||
# Design / assets temp files (e.g. Illustrator)
|
||||
resources/**/*.tmp
|
||||
|
||||
# VitePress
|
||||
docs/.vitepress/dist
|
||||
docs/.vitepress/cache
|
||||
|
||||
37
CLAUDE.md
37
CLAUDE.md
@@ -4,7 +4,7 @@
|
||||
|
||||
Crewli is a multi-tenant SaaS platform for event and festival management.
|
||||
Built for a professional volunteer organisation, with potential to expand as SaaS.
|
||||
Design document: `/resources/design/design-document.md`
|
||||
Design document: `/dev-docs/design-document.md`
|
||||
|
||||
## Tech stack
|
||||
|
||||
@@ -130,3 +130,38 @@ Configure three frontend origins in both Laravel (`config/cors.php` via env) and
|
||||
11. Pinia store if cross-component state is needed
|
||||
12. Vue page component
|
||||
13. Add route in Vue Router
|
||||
|
||||
## User Documentation (VitePress)
|
||||
|
||||
End-user documentation lives in /docs/ as a VitePress site.
|
||||
Developer documentation (SCHEMA.md, API.md, etc.) lives in /dev-docs/.
|
||||
|
||||
### When to write docs
|
||||
|
||||
When completing a feature that introduces or changes **user-facing behaviour**,
|
||||
create or update the corresponding documentation page under /docs/.
|
||||
|
||||
### How to write docs
|
||||
|
||||
1. Read /docs/.templates/style-guide.md for terminology, tone, and structure rules
|
||||
2. Use /docs/.templates/feature-page.md or concept-page.md as your starting template
|
||||
3. Every page MUST have frontmatter with: title, description, tags
|
||||
4. Use Dutch language for all content (informal "je/jij")
|
||||
5. Use Crewli terminology from the style guide — never English equivalents in user docs
|
||||
|
||||
### What to include
|
||||
|
||||
- What the feature does (2-3 sentences)
|
||||
- Step-by-step instructions from the user's perspective
|
||||
- Which roles have access (table format)
|
||||
- Screenshot placeholders where visual guidance helps: ``
|
||||
- Links to related pages
|
||||
|
||||
### File placement
|
||||
|
||||
Match the existing structure under /docs/:
|
||||
|
||||
- Organizer features → /docs/organizer/[category]/
|
||||
- Volunteer features → /docs/volunteer/
|
||||
- Portal features → /docs/portal/
|
||||
- General concepts → /docs/guide/
|
||||
|
||||
165
dev-docs/API.md
Normal file
165
dev-docs/API.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# Crewli API Contract
|
||||
|
||||
Base path: `/api/v1/`
|
||||
|
||||
Auth: Bearer token (Sanctum)
|
||||
|
||||
## Auth
|
||||
|
||||
- `POST /auth/login`
|
||||
- `POST /auth/logout`
|
||||
- `GET /auth/me`
|
||||
|
||||
## Organisations
|
||||
|
||||
- `GET /organisations` — list (super admin)
|
||||
- `POST /organisations` — create
|
||||
- `GET /organisations/{org}` — show
|
||||
- `PUT /organisations/{org}` — update
|
||||
- `GET /organisations/{org}/members` — members
|
||||
- `POST /organisations/{org}/invite` — invite user
|
||||
|
||||
## Events
|
||||
|
||||
- `GET /organisations/{org}/events` — list (top-level only by default)
|
||||
- `GET /organisations/{org}/events?include_children=true` — include sub-events nested in response
|
||||
- `GET /organisations/{org}/events?type=festival` — filter by event_type (festival|series|event)
|
||||
- `POST /organisations/{org}/events` — create (supports `parent_event_id` for sub-events)
|
||||
- `GET /organisations/{org}/events/{event}` — detail (includes children and parent if loaded)
|
||||
- `PUT /organisations/{org}/events/{event}` — update (does NOT accept `status` — use transition endpoint)
|
||||
- `POST /organisations/{org}/events/{event}/transition` — change event status via state machine (see below)
|
||||
- `GET /organisations/{org}/events/{event}/children` — list sub-events of a festival/series
|
||||
|
||||
### Event Status Transitions
|
||||
|
||||
`POST /organisations/{org}/events/{event}/transition`
|
||||
|
||||
Body: `{ "status": "published" }`
|
||||
|
||||
Enforces a state machine: only valid forward (and select backward) transitions are allowed.
|
||||
Returns 422 with `errors`, `current_status`, `requested_status`, and `allowed_transitions` when the transition is invalid or prerequisites are missing.
|
||||
|
||||
**Prerequisites checked:**
|
||||
- `→ published`: name, start_date, end_date required
|
||||
- `→ registration_open`: at least one time slot and one section required
|
||||
|
||||
**Festival cascade:** Transitioning a festival parent to `showday`, `teardown`, or `closed` automatically cascades to all children in an earlier status.
|
||||
|
||||
**EventResource** includes `allowed_transitions` (array of valid next statuses) so the frontend knows which buttons to show.
|
||||
|
||||
## Crowd Types
|
||||
|
||||
- `GET /organisations/{org}/crowd-types`
|
||||
- `POST /organisations/{org}/crowd-types`
|
||||
- `PUT /organisations/{org}/crowd-types/{type}`
|
||||
- `DELETE /organisations/{org}/crowd-types/{type}`
|
||||
|
||||
## Companies
|
||||
|
||||
- `GET /organisations/{org}/companies`
|
||||
- `POST /organisations/{org}/companies`
|
||||
- `PUT /organisations/{org}/companies/{company}`
|
||||
- `DELETE /organisations/{org}/companies/{company}`
|
||||
|
||||
## Section Categories
|
||||
|
||||
- `GET /organisations/{org}/section-categories` — distinct categories used across the organisation's events (for autocomplete). Returns `{ "data": ["Bar", "Podium", ...] }`
|
||||
|
||||
## Festival Sections
|
||||
|
||||
- `GET /events/{event}/sections`
|
||||
- `POST /events/{event}/sections`
|
||||
- `PUT /events/{event}/sections/{section}`
|
||||
- `DELETE /events/{event}/sections/{section}`
|
||||
- `POST /events/{event}/sections/reorder`
|
||||
|
||||
> **Festival context:** `{event}` can be a festival parent or a sub-event.
|
||||
> On a festival parent, sections are for operational planning (build-up, teardown).
|
||||
> For sub-events, `GET` automatically includes `cross_event` sections from the parent festival.
|
||||
> Shifts on cross_event sections must use the **parent festival's event_id** in API calls,
|
||||
> since the section's `event_id` points to the parent.
|
||||
|
||||
## Time Slots
|
||||
|
||||
- `GET /events/{event}/time-slots`
|
||||
- `POST /events/{event}/time-slots`
|
||||
- `PUT /events/{event}/time-slots/{timeSlot}`
|
||||
- `DELETE /events/{event}/time-slots/{timeSlot}`
|
||||
|
||||
> **Festival context:** `{event}` can be a festival parent or a sub-event.
|
||||
> Festival-level time slots (operational: build-up, teardown, transitions) are separate
|
||||
> from sub-event time slots (program-specific).
|
||||
>
|
||||
> `GET /events/{event}/time-slots` returns only the specified event's own time slots by
|
||||
> default. For sub-events, pass `?include_parent=true` to also include the parent festival's
|
||||
> time slots — each time slot is marked with a `source` field (`sub_event` or `festival`)
|
||||
> and includes `event_name` for display grouping. This parameter has no effect on festivals
|
||||
> or flat events.
|
||||
>
|
||||
> Shifts on sub-event sections may reference parent festival time slots (e.g. for build-up
|
||||
> shifts). The `time_slot_id` validation accepts time slots from the sub-event itself or
|
||||
> its parent festival.
|
||||
|
||||
## Shifts
|
||||
|
||||
- `GET /events/{event}/sections/{section}/shifts`
|
||||
- `POST /events/{event}/sections/{section}/shifts`
|
||||
- `PUT /events/{event}/sections/{section}/shifts/{shift}`
|
||||
- `DELETE /events/{event}/sections/{section}/shifts/{shift}`
|
||||
- `POST /events/{event}/sections/{section}/shifts/{shift}/assign`
|
||||
- `POST /events/{event}/sections/{section}/shifts/{shift}/claim`
|
||||
|
||||
> **Festival context:** When managing shifts on a `cross_event` section, the `{event}`
|
||||
> in the URL must be the parent festival's ID (matching `section.event_id`), not the
|
||||
> sub-event's ID.
|
||||
|
||||
## Persons
|
||||
|
||||
- `GET /events/{event}/persons`
|
||||
- `POST /events/{event}/persons`
|
||||
- `GET /events/{event}/persons/{person}`
|
||||
- `PUT /events/{event}/persons/{person}`
|
||||
- `POST /events/{event}/persons/{person}/approve`
|
||||
- `DELETE /events/{event}/persons/{person}`
|
||||
|
||||
## Crowd Lists
|
||||
|
||||
- `GET /events/{event}/crowd-lists`
|
||||
- `POST /events/{event}/crowd-lists`
|
||||
- `PUT /events/{event}/crowd-lists/{list}`
|
||||
- `DELETE /events/{event}/crowd-lists/{list}`
|
||||
- `POST /events/{event}/crowd-lists/{list}/persons`
|
||||
- `DELETE /events/{event}/crowd-lists/{list}/persons/{person}`
|
||||
|
||||
## Locations
|
||||
|
||||
- `GET /events/{event}/locations`
|
||||
- `POST /events/{event}/locations`
|
||||
- `PUT /events/{event}/locations/{location}`
|
||||
- `DELETE /events/{event}/locations/{location}`
|
||||
|
||||
## Person Tags (Organisation Settings)
|
||||
|
||||
- `GET /organisations/{org}/person-tags` — list active tags (ordered)
|
||||
- `POST /organisations/{org}/person-tags` — create tag
|
||||
- `PUT /organisations/{org}/person-tags/{tag}` — update tag
|
||||
- `DELETE /organisations/{org}/person-tags/{tag}` — deactivate tag (soft: sets `is_active = false`)
|
||||
- `GET /organisations/{org}/person-tag-categories` — distinct categories for autocomplete
|
||||
|
||||
## User Tag Assignments
|
||||
|
||||
- `GET /organisations/{org}/users/{user}/tags` — list all tags for user in organisation
|
||||
- `POST /organisations/{org}/users/{user}/tags` — assign a tag
|
||||
- `DELETE /organisations/{org}/users/{user}/tags/{tagAssignment}` — remove assignment
|
||||
- `PUT /organisations/{org}/users/{user}/tags/sync` — sync tags by source
|
||||
|
||||
> **Sync endpoint:** Replaces tags of the specified `source` only.
|
||||
> Body: `{ "tag_ids": ["ulid1", "ulid2"], "source": "self_reported" }`
|
||||
> Removes `self_reported` tags not in the list, adds new ones, leaves `organiser_assigned` untouched (and vice versa).
|
||||
|
||||
### Person list tag filtering
|
||||
|
||||
- `GET /events/{event}/persons?tag={person_tag_id}` — filter persons by single tag
|
||||
- `GET /events/{event}/persons?tags=ulid1,ulid2` — filter persons by multiple tags (AND logic: must have all)
|
||||
|
||||
_(Extend this contract per module as endpoints are implemented.)_
|
||||
@@ -84,6 +84,25 @@ op basis van: reliability score, aantal uren al ingepland, aanmeldvolgorde.
|
||||
|
||||
---
|
||||
|
||||
### ARCH-06 — Locatie-gebaseerd shift-overzicht
|
||||
|
||||
Cross sub-event filter op location_id. Toont alle shifts op een fysieke locatie
|
||||
ongeacht programmaonderdeel.
|
||||
**Schema:** `locations` tabel en `shifts.location_id` bestaan al.
|
||||
**Prioriteit:** Laag
|
||||
|
||||
---
|
||||
|
||||
### ARCH-07 — Accreditatie-templates per sectie/dag combinatie
|
||||
|
||||
MUST-HAVE bij accreditatie build. Templates worden primaire toewijzingsmethode.
|
||||
Per crowd_type + sectie + dag → automatisch voorgestelde accreditatie-items.
|
||||
Handmatige per-persoon toewijzing is de uitzondering, niet de norm.
|
||||
**Schema:** Nieuwe tabel `accreditation_templates` nodig.
|
||||
**Prioriteit:** Hoog — direct meebouwen bij accreditatie-module
|
||||
|
||||
---
|
||||
|
||||
## Fase 3 — Communicatie & Notificaties
|
||||
|
||||
### COMM-01 — Real-time WebSocket notificaties
|
||||
@@ -380,5 +399,87 @@ realistische testdata nodig met parent/child events.
|
||||
|
||||
---
|
||||
|
||||
### ~~TECH-04 — EventController.store() redundante ternary~~ ✅ OPGELOST
|
||||
|
||||
---
|
||||
|
||||
## Opgeloste items (april 2026)
|
||||
|
||||
De volgende items zijn geïmplementeerd en afgerond:
|
||||
|
||||
- ~~TECH-04: EventController.store() redundante ternary~~ ✅
|
||||
- ~~Auth race condition (CTRL+R fix)~~ ✅
|
||||
- ~~Section edit dialog bug~~ ✅
|
||||
- ~~Time slot duplicate button~~ ✅
|
||||
- ~~Browser autocomplete disabled op dialog form fields~~ ✅
|
||||
- ~~Category + icon fields op festival_sections~~ ✅
|
||||
- ~~IconPicker component~~ ✅
|
||||
- ~~Crowd Types beheer-UI~~ ✅
|
||||
- ~~Companies CRUD~~ ✅
|
||||
- ~~Person tags backend (person_tags + user_organisation_tags)~~ ✅
|
||||
- ~~Event status state machine (dedicated transition endpoint, prerequisites, festival cascade)~~ ✅
|
||||
- ~~Festival tab-navigatie (uniform tabs, Programmaonderdelen tab)~~ ✅
|
||||
- ~~SectionsShiftsPanel extractie als herbruikbaar component~~ ✅
|
||||
- ~~Cross-event section auto-redirect~~ ✅
|
||||
|
||||
---
|
||||
|
||||
## Nieuwe backlog items
|
||||
|
||||
### ARCH-06 — Locatie-gebaseerd shift-overzicht
|
||||
|
||||
Cross sub-event filter op location_id. Toont alle shifts op een fysieke locatie
|
||||
ongeacht programmaonderdeel.
|
||||
**Prioriteit:** Laag
|
||||
|
||||
---
|
||||
|
||||
### ARCH-07 — Accreditatie-templates per sectie/dag combinatie
|
||||
|
||||
MUST-HAVE bij accreditatie build. Templates worden primaire toewijzingsmethode.
|
||||
Per crowd_type + sectie + dag → automatisch voorgestelde accreditatie-items.
|
||||
**Prioriteit:** Hoog — direct meebouwen bij accreditatie-module
|
||||
|
||||
---
|
||||
|
||||
### ARCH-08 — Recurrence voor time slots
|
||||
|
||||
Herhalingsfunctie: "genereer 5 time slots in één keer" voor opbouwdagen etc.
|
||||
**Prioriteit:** Middel
|
||||
|
||||
---
|
||||
|
||||
### ART-03 — Artist profile met cross-event rider defaults
|
||||
|
||||
Organisatie-niveau artiest-profiel dat rider-defaults, contacten en interne
|
||||
notities opslaat over events heen. "Importeer van vorig jaar" functie.
|
||||
**Prioriteit:** Laag
|
||||
|
||||
---
|
||||
|
||||
### UX-01 — Festival setup checklist / onboarding wizard
|
||||
|
||||
Checklist widget op festival dashboard die door de configuratiestappen leidt.
|
||||
Items worden groen als ze zijn afgerond.
|
||||
**Prioriteit:** Middel
|
||||
|
||||
---
|
||||
|
||||
### UX-02 — Aandachtsmatrix dashboard
|
||||
|
||||
Dashboard widget: hoeveel personen approved maar zonder shift? Hoeveel
|
||||
shift-claims wachten op goedkeuring? Hoeveel pending identity matches?
|
||||
**Prioriteit:** Middel
|
||||
|
||||
---
|
||||
|
||||
### UX-03 — Personen-tab op sub-event niveau
|
||||
|
||||
Gefilterde view: alleen personen met shifts in dit programmaonderdeel.
|
||||
Met link "Bekijk alle personen op festival-niveau".
|
||||
**Prioriteit:** Middel
|
||||
|
||||
---
|
||||
|
||||
_Laatste update: April 2026_
|
||||
_Voeg nieuwe items toe met prefix: ARCH-, COMM-, OPS-, VOL-, ART-, FORM-, SUP-, DIFF-, APPS-, TECH-_
|
||||
_Voeg nieuwe items toe met prefix: ARCH-, COMM-, OPS-, VOL-, ART-, FORM-, SUP-, DIFF-, APPS-, TECH-, UX-_
|
||||
@@ -41,7 +41,8 @@
|
||||
7. [3.5.7 Artists & Advancing](#357-artists--advancing)
|
||||
8. [3.5.8 Communication & Briefings](#358-communication--briefings)
|
||||
9. [3.5.9 Forms, Check-In & Operational](#359-forms-check-in--operational)
|
||||
10. [3.5.10 Database Design Rules & Index Strategy](#3510-database-design-rules--index-strategy)
|
||||
10. [3.5.10 Person Tags & Skills](#3510-person-tags--skills)
|
||||
11. [3.5.11 Database Design Rules & Index Strategy](#3511-database-design-rules--index-strategy)
|
||||
|
||||
---
|
||||
|
||||
@@ -162,6 +163,33 @@
|
||||
**Indexes:** `(organisation_id, status)`, `(parent_event_id)`, `UNIQUE(organisation_id, slug)`
|
||||
**Soft delete:** yes
|
||||
|
||||
**Status state machine:**
|
||||
|
||||
```
|
||||
draft → published → registration_open → buildup → showday → teardown → closed
|
||||
↑ ↓ ↑
|
||||
└── (back) ────┘ │
|
||||
(back) ────┘
|
||||
```
|
||||
|
||||
Allowed transitions:
|
||||
|
||||
| From | To |
|
||||
| ------------------- | --------------------------- |
|
||||
| `draft` | `published` |
|
||||
| `published` | `registration_open`, `draft`|
|
||||
| `registration_open` | `buildup`, `published` |
|
||||
| `buildup` | `showday` |
|
||||
| `showday` | `teardown` |
|
||||
| `teardown` | `closed` |
|
||||
| `closed` | _(terminal — no transitions)_ |
|
||||
|
||||
**Prerequisites:**
|
||||
- `→ published`: name, start_date, and end_date must be set
|
||||
- `→ registration_open`: at least one time slot and one section must exist
|
||||
|
||||
**Festival cascade:** When a festival parent transitions to `showday`, `teardown`, or `closed`, all children in an earlier status are automatically updated to the same status. Earlier statuses (`draft → published`) do NOT cascade.
|
||||
|
||||
**Helper scopes (Laravel):**
|
||||
|
||||
```php
|
||||
@@ -254,6 +282,8 @@ scopeFestivals() // WHERE event_type IN ('festival', 'series')
|
||||
| `id` | ULID | PK |
|
||||
| `event_id` | ULID FK | → events |
|
||||
| `name` | string | e.g. Horeca, Backstage, Overig, Entertainment |
|
||||
| `category` | string nullable | **v1.8** Free-text grouping label (e.g. Bar, Podium, Operationeel)|
|
||||
| `icon` | string nullable | **v1.8** Tabler icon name (e.g. tabler-beer, tabler-microphone-2) |
|
||||
| `type` | enum | `standard\|cross_event` — cross_event for EHBO, verkeersregelaars |
|
||||
| `sort_order` | int | default: 0 |
|
||||
| `crew_need` | int nullable | **v1.5** Total crew needed for this section (Crescat: Crew need) |
|
||||
@@ -267,7 +297,7 @@ scopeFestivals() // WHERE event_type IN ('festival', 'series')
|
||||
| `deleted_at` | timestamp nullable | Soft delete |
|
||||
|
||||
**Relations:** `hasMany` shifts
|
||||
**Indexes:** `(event_id, sort_order)`
|
||||
**Indexes:** `(event_id, sort_order)`, `(event_id, category)`
|
||||
**Soft delete:** yes
|
||||
|
||||
**Default values:**
|
||||
@@ -279,6 +309,16 @@ scopeFestivals() // WHERE event_type IN ('festival', 'series')
|
||||
- `responder_self_checkin`: true
|
||||
- `timed_accreditations`: false
|
||||
|
||||
> **Note:** "Overkoepelende" sections (shared across all sub-events of a festival)
|
||||
> are identified by `type = 'cross_event'`. There is no separate `is_shared` boolean
|
||||
> column — the `type` enum distinguishes standard sections from cross-event sections.
|
||||
|
||||
**Festival context:** On a festival parent event, `standard` type sections are festival-only
|
||||
operational sections (e.g. Terreinploeg, Bouwploeg). `cross_event` sections appear in all
|
||||
sub-events (e.g. EHBO, Security, Backstage). On sub-events, all sections are `standard`
|
||||
(program-specific). When querying sections for a sub-event, the API automatically includes
|
||||
`cross_event` sections from the parent festival.
|
||||
|
||||
---
|
||||
|
||||
### `time_slots`
|
||||
@@ -287,6 +327,15 @@ scopeFestivals() // WHERE event_type IN ('festival', 'series')
|
||||
> This naturally handles "generic shifts" — multiple sections referencing one Time Slot share the same time framework.
|
||||
> Per-shift time overrides are handled by `shifts.actual_start_time` / `actual_end_time`.
|
||||
|
||||
**Festival context:** Time slots on a festival parent event are for operational scheduling
|
||||
(build-up, teardown, transitions). Time slots on sub-events are for program-specific
|
||||
scheduling. When querying time slots for a sub-event with `?include_parent=true`, the parent
|
||||
festival's time slots are included (marked with `source='festival'`) so that local sections
|
||||
can create build-up, teardown, and transition shifts. Without this parameter, only the
|
||||
sub-event's own time slots are returned. Festival-level queries never include sub-event
|
||||
time slots. The `Event::getAllRelevantTimeSlots()` method can retrieve time slots across
|
||||
levels for planning purposes.
|
||||
|
||||
| Column | Type | Notes |
|
||||
| ---------------- | --------------------- | --------------------------------------------------------------------- |
|
||||
| `id` | ULID | PK |
|
||||
@@ -1271,7 +1320,68 @@ $effectiveDate = $shift->end_date ?? $shift->timeSlot->date;
|
||||
|
||||
---
|
||||
|
||||
## 3.5.10 Database Design Rules & Index Strategy
|
||||
## 3.5.10 Person Tags & Skills
|
||||
|
||||
> Tag-based skills/competencies system for volunteers and crew.
|
||||
> Tags are defined per organisation, assigned to users at the organisation level
|
||||
> (persistent across events), and come from two sources: self-reported by
|
||||
> volunteers during registration, or assigned by organisers based on experience.
|
||||
|
||||
### `person_tags`
|
||||
|
||||
| Column | Type | Notes |
|
||||
| ----------------- | --------------- | ---------------------------------------- |
|
||||
| `id` | ULID | PK, `HasUlids` trait |
|
||||
| `organisation_id` | ULID FK | → organisations |
|
||||
| `name` | string(50) | e.g. "Tapper", "EHBO", "Duits" |
|
||||
| `category` | string(50) null | e.g. "Vaardigheid", "Taal", "Certificaat"|
|
||||
| `icon` | string(50) null | Tabler icon name |
|
||||
| `color` | string(7) null | Hex color |
|
||||
| `is_active` | bool | default: true |
|
||||
| `sort_order` | int | default: 0 |
|
||||
| `created_at` | timestamp | |
|
||||
| `updated_at` | timestamp | |
|
||||
|
||||
**Relations:** `belongsTo` Organisation
|
||||
**Indexes:** `(organisation_id, is_active, sort_order)`
|
||||
**Unique constraint:** `UNIQUE(organisation_id, name)`
|
||||
**No soft deletes** — tags are deactivated via `is_active = false`
|
||||
|
||||
---
|
||||
|
||||
### `user_organisation_tags`
|
||||
|
||||
> Tag assignments linking a user to a tag within an organisation.
|
||||
> Persistent across events — tags live on user+org level, not event level.
|
||||
> Users without a `user_id` (external guests, artists) cannot have tags.
|
||||
|
||||
| Column | Type | Notes |
|
||||
| -------------------- | --------------- | ---------------------------------------------- |
|
||||
| `id` | int AI | PK — integer for join performance (pivot table) |
|
||||
| `user_id` | ULID FK | → users |
|
||||
| `organisation_id` | ULID FK | → organisations |
|
||||
| `person_tag_id` | ULID FK | → person_tags |
|
||||
| `source` | enum | `self_reported\|organiser_assigned` |
|
||||
| `assigned_by_user_id`| ULID FK null | → users (who assigned, for organiser_assigned) |
|
||||
| `proficiency` | enum null | `beginner\|experienced\|expert` |
|
||||
| `notes` | text null | Organiser-only notes, never shown to volunteer |
|
||||
| `assigned_at` | timestamp | |
|
||||
|
||||
**Relations:** `belongsTo` User, Organisation, PersonTag, AssignedBy (→ User)
|
||||
**Unique constraint:** `UNIQUE(user_id, organisation_id, person_tag_id, source)`
|
||||
— allows both `self_reported` AND `organiser_assigned` for the same tag
|
||||
**Indexes:** `(user_id, organisation_id)`, `(person_tag_id)`, `(organisation_id, person_tag_id, proficiency)`
|
||||
|
||||
**Design notes:**
|
||||
|
||||
- Tags are scoped to `organisation_id` — organisation A's "Tapper" tag is independent of organisation B's.
|
||||
- Tags link to `user_id`, NOT `person_id` — this makes them persistent across events.
|
||||
- The unique constraint includes `source` — a volunteer can self-report "Tapper" AND the organiser can independently assign "Tapper expert". Both coexist.
|
||||
- **Sync behaviour:** The `PUT .../tags/sync` endpoint replaces tags of the specified `source` only. Syncing `self_reported` tags removes self_reported tags not in the new list and adds new ones, while leaving all `organiser_assigned` tags untouched.
|
||||
|
||||
---
|
||||
|
||||
## 3.5.11 Database Design Rules & Index Strategy
|
||||
|
||||
### Rule 1 — ULID as Primary Key
|
||||
|
||||
@@ -4,7 +4,7 @@ Product Design & Technical Specification
|
||||
|
||||
Full Stack SaaS — Event & Festival Management Platform
|
||||
|
||||
**Version:** 1.3 | **Datum:** Maart 2026 | **Status:** Concept | **Tech Stack:** Laravel 12 + Vue 3
|
||||
**Version:** 1.8 | **Datum:** April 2026 | **Status:** Concept | **Tech Stack:** Laravel 12 + Vue 3
|
||||
|
||||
# 1. Product Vision & Scope
|
||||
|
||||
@@ -42,7 +42,11 @@ De volgende termen worden door het hele document en de codebase consistent gehan
|
||||
| **Term** | **Definitie** |
|
||||
|----|----|
|
||||
| Organisation | Een klant/organisatie die Crewli gebruikt. Heeft eigen evenementen, teamleden en instellingen. Equivalent aan 'Launchpad' in In2Event. |
|
||||
| Event | Een specifiek evenement of festival binnen een organisatie. Alle operationele data is event-scoped. |
|
||||
| Event | Een specifiek evenement of festival binnen een organisatie. Alle operationele data is event-scoped. Kan een enkelvoudig event, festival of serie zijn (zie event type model, sectie 3.4.1). |
|
||||
| Festival | Een event van type 'festival' dat programmaonderdelen (sub-events) bevat. Heeft zelf geen secties of shifts — die zitten op de programmaonderdelen. |
|
||||
| Serie | Vergelijkbaar met Festival, maar voor terugkerende events (edities). Zelfde parent-child structuur. |
|
||||
| Programmaonderdeel | Een sub-event binnen een festival of serie. Heeft eigen secties, time slots, shifts, personen en artiesten. De benaming is configureerbaar per festival (Dag, Programmaonderdeel, Editie, Locatie, Ronde). |
|
||||
| Overkoepelende sectie | Een festival section met type 'cross_event' die actief is over alle programmaonderdelen van een festival (bijv. EHBO, Security). |
|
||||
| Festival Section | Een operationele eenheid binnen een event: Bar, Hospitality, Technical, Security, etc. Heeft eigen Crew, Volunteers en Shifts. |
|
||||
| Crowd Type | Classificatie van deelnemers: Crew, Volunteer, Artist, Guest, Press, Partner, Supplier. Configureerbaar per organisatie. |
|
||||
| Person | Individuele deelnemer gekoppeld aan een event via een Crowd Type. |
|
||||
@@ -232,6 +236,83 @@ Elk evenement doorloopt een vaste lifecycle. De dashboard UI en beschikbare acti
|
||||
| Tear-Down | Afbouwdagen. Inventaris terugname. Afsluiting van shifts. |
|
||||
| Closed | Evenement afgerond. Read-only. Rapporten beschikbaar. Data wordt gearchiveerd. |
|
||||
|
||||
### 3.4.1 Event Type Model (v1.7)
|
||||
|
||||
Crewli ondersteunt drie event-modi via het universele event model:
|
||||
|
||||
**Flat event** (`event_type = 'event'`, geen parent, geen children) — een standaard enkelvoudig evenement. Alle modules (secties, time slots, shifts, personen, artiesten) zitten direct op het event.
|
||||
|
||||
**Festival** (`event_type = 'festival'`, geen parent, heeft children) — een container met programmaonderdelen. Het festival heeft twee planningslagen: operationele planning op festival-niveau (opbouw, afbraak, transitie) en programma-planning op de programmaonderdelen (sub-events). Personen worden op festival-niveau beheerd en kunnen per programmaonderdeel geactiveerd worden. De benaming van programmaonderdelen is configureerbaar via `sub_event_label`: Dag, Programmaonderdeel, Editie, Locatie, Ronde, of een eigen naam.
|
||||
|
||||
**Serie** (`event_type = 'series'`, geen parent, heeft children) — functioneel identiek aan een festival, maar bedoeld voor terugkerende events (edities/rondes). Gebruikt dezelfde parent-child structuur.
|
||||
|
||||
Sub-events (`parent_event_id` ingevuld) zijn de operationele eenheden binnen een festival of serie. Elk sub-event heeft een eigen event-record met eigen secties, time slots, shifts en artiesten. Overkoepelende secties (bijv. EHBO, Security) worden aangemaakt met type `cross_event` en zijn actief over alle programmaonderdelen.
|
||||
|
||||
**Cross-event sectie auto-redirect (v1.8):** Wanneer een gebruiker een `cross_event` sectie aanmaakt op een sub-event, wordt deze automatisch aangemaakt op het parent festival (met redirect-meta in de response). Zo verschijnt de sectie direct in alle programmaonderdelen. Op een flat event is `cross_event` type niet toegestaan (422).
|
||||
|
||||
**Cross-level time slot gebruik (v1.8):** Shifts op sub-event secties mogen verwijzen naar time slots van zowel het eigen sub-event als het parent festival. Dit maakt opbouw-shifts mogelijk op programma-specifieke secties. De omgekeerde richting (festival-shifts verwijzen naar sub-event time slots) is niet toegestaan.
|
||||
|
||||
| | |
|
||||
|:--:|----|
|
||||
| **UI GEDRAG** | Als een event geen children heeft, worden alle tabs op event-niveau getoond (flat mode). Zodra children worden toegevoegd, wordt het event een festival-container en krijgen de children de operationele tabs. |
|
||||
|
||||
### Festival-level operationele planning
|
||||
|
||||
Een festival (parent event) heeft twee planningslagen:
|
||||
|
||||
1. **Operationele planning** — direct op het festival:
|
||||
opbouw, afbraak, nachtsecurity, transitieperiodes tussen programmaonderdelen.
|
||||
Gebruikt festival-level time slots en festival-level secties.
|
||||
|
||||
2. **Programma-planning** — op de programmaonderdelen (sub-events):
|
||||
show-specifieke secties, programma-specifieke time slots en shifts.
|
||||
|
||||
Overkoepelende secties (`type = cross_event`) zijn zichtbaar in zowel de
|
||||
festival-level operationele planning als in alle programmaonderdelen.
|
||||
Standaard secties op festival-niveau (bijv. "Terreinploeg") zijn alleen
|
||||
zichtbaar op festival-niveau.
|
||||
|
||||
| Sectie-type | Op festival-parent | Zichtbaar in sub-events? |
|
||||
|---|---|---|
|
||||
| `standard` | Operationele secties (Terreinploeg, Bouwploeg) | Nee — alleen op festival-niveau |
|
||||
| `cross_event` | Overkoepelend (EHBO, Security, Backstage) | Ja — in alle programmaonderdelen |
|
||||
| `standard` (op sub-event) | Programma-specifiek (Bar Schirmbar) | Alleen in eigen sub-event |
|
||||
|
||||
Festival-level time slots (bijv. "Opbouw dag 1", "Afbraak") worden **niet** getoond
|
||||
wanneer time slots voor een sub-event worden opgevraagd. Elk niveau heeft zijn eigen
|
||||
tijdvensters.
|
||||
|
||||
### 3.4.2 Event Status State Machine (v1.8)
|
||||
|
||||
Statuswijzigingen op events worden afgedwongen via een state machine. Alleen gedefinieerde transities zijn toegestaan — de reguliere PUT /events/{event} endpoint accepteert **geen** `status` veld. Statuswijzigingen gaan uitsluitend via het dedicated endpoint.
|
||||
|
||||
**Endpoint:** `POST /organisations/{org}/events/{event}/transition`
|
||||
**Body:** `{ "status": "published" }`
|
||||
|
||||
**Transitiekaart:**
|
||||
|
||||
| Van | Naar |
|
||||
|---------------------|------------------------------|
|
||||
| `draft` | `published` |
|
||||
| `published` | `registration_open`, `draft` |
|
||||
| `registration_open` | `buildup`, `published` |
|
||||
| `buildup` | `showday` |
|
||||
| `showday` | `teardown` |
|
||||
| `teardown` | `closed` |
|
||||
| `closed` | _(terminal — geen transities)_ |
|
||||
|
||||
Beperkte achterwaartse stappen: `published → draft` (voor correcties) en `registration_open → published` (om registratie te sluiten).
|
||||
|
||||
**Prerequisites per transitie:**
|
||||
- `→ published`: name, start_date en end_date moeten ingevuld zijn
|
||||
- `→ registration_open`: minimaal 1 time slot en 1 sectie moeten bestaan
|
||||
|
||||
Bij een ongeldige transitie of ontbrekende prerequisites retourneert de endpoint 422 met `errors`, `current_status`, `requested_status` en `allowed_transitions`.
|
||||
|
||||
**EventResource** bevat altijd een `allowed_transitions` array zodat de frontend weet welke statusknoppen beschikbaar zijn.
|
||||
|
||||
**Festival cascade:** Wanneer een festival parent naar `showday`, `teardown` of `closed` transitiërt, worden alle children in een eerdere status automatisch meegezet naar dezelfde status. Eerdere statuswijzigingen (`draft → published`) cascaden **niet** — sub-events kunnen op een eigen planning zitten.
|
||||
|
||||
## 3.5 Core Database Schema
|
||||
|
||||
Onderstaand het volledige, productie-waardige datamodel. Alle 12 bevindingen uit de database review (v1.3) zijn verwerkt. Tabellen zijn gegroepeerd per domein.
|
||||
@@ -248,7 +329,7 @@ Onderstaand het volledige, productie-waardige datamodel. Alle 12 bevindingen uit
|
||||
| **organisations** | id (ULID), name, slug, billing_status, settings (JSON: display prefs only), created_at, deleted_at | hasMany events, crowd_types, accreditation_categories. settings JSON alleen voor opaque UI-config, niet voor queryable data. Soft delete: ja. |
|
||||
| **organisation_user** | id (int AI), user_id, organisation_id, role | Pivot. Integer PK voor join-performance. FK: users, organisations. Spatie role via pivot. |
|
||||
| **user_invitations** | id (ULID), email, invited_by_user_id, organisation_id, event_id (nullable), role, token (ULID, unique), status (pending\|accepted\|expired), expires_at | Token in uitnodigingsmail. Bij accept: zoek bestaand account op email of maak nieuw aan. INDEX: (token), (email, status). |
|
||||
| **events** | id (ULID), organisation_id, name, slug, start_date, end_date, timezone, status (draft\|published\|registration_open\|buildup\|showday\|teardown\|closed), deleted_at | belongsTo organisation. hasMany festival_sections, time_slots, persons, artists, briefings. Soft delete: ja. INDEX: (organisation_id, status). |
|
||||
| **events** | id (ULID), organisation_id, parent_event_id (ULID FK nullable → events, nullOnDelete), name, slug, start_date, end_date, timezone, status (draft\|published\|registration_open\|buildup\|showday\|teardown\|closed), event_type (enum: event\|festival\|series, default: event), event_type_label (string nullable), sub_event_label (string nullable), is_recurring (bool, default: false), recurrence_rule (string nullable), recurrence_exceptions (JSON nullable), deleted_at | belongsTo organisation. belongsTo event as parent (parent_event_id). hasMany events as children (parent_event_id). hasMany festival_sections, time_slots, persons, artists, briefings. Soft delete: ja. INDEX: (organisation_id, status), (parent_event_id), UNIQUE(organisation_id, slug). Zie sectie 3.4.1 voor event type model. |
|
||||
| **event_user_roles** | id (int AI), user_id, event_id, role | Pivot. Integer PK. FK: users, events. |
|
||||
|
||||
### 3.5.2 Locaties (nieuw — oplossing probleem 3)
|
||||
@@ -265,7 +346,7 @@ Drielaags Crescat-model. Kritieke verbetering: time_slot_id gedenormaliseerd naa
|
||||
|
||||
| **Tabel** | **Belangrijkste kolommen** | **Relaties, constraints & opmerkingen** |
|
||||
|----|----|----|
|
||||
| **festival_sections** | id (ULID), event_id, name, sort_order, deleted_at | hasMany shifts. Soft delete: ja. INDEX: (event_id, sort_order). |
|
||||
| **festival_sections** | id (ULID), event_id, name, category (string nullable — v1.8), icon (string nullable — v1.8), type (standard\|cross_event), sort_order, crew_need, crew_auto_accepts, crew_invited_to_events, added_to_timeline, responder_self_checkin, crew_accreditation_level, public_form_accreditation_level, timed_accreditations, deleted_at | hasMany shifts. category: vrije tekst groepslabel met autocomplete vanuit eerder gebruikte waarden binnen de organisatie (endpoint: GET /organisations/{org}/section-categories). icon: Tabler icon naam, geselecteerd via IconPicker component. Soft delete: ja. INDEX: (event_id, sort_order), (event_id, category). |
|
||||
| **time_slots** | id (ULID), event_id, name, person_type (CREW\|VOLUNTEER\|PRESS\|PHOTO\|PARTNER), date, start_time, end_time, duration_hours | hasMany shifts. person_type bepaalt zichtbaarheid in registratieformulier. INDEX: (event_id, person_type, date). |
|
||||
| **shifts** | id (ULID), festival_section_id, time_slot_id, location_id, slots_total, slots_open_for_claiming, assigned_crew_id, events_during_shift (JSON: array of performance_ids), status, deleted_at | belongsTo festival_section, time_slot, location. hasMany shift_assignments. JSON hier OK: performance_ids zijn opaque referentie-lijst zonder filtering. INDEX: (festival_section_id, time_slot_id), (time_slot_id, status). Soft delete: ja. |
|
||||
| **shift_assignments** | id (ULID), shift_id, person_id, time_slot_id (FK: gedenormaliseerd), status (pending_approval\|approved\|rejected\|cancelled\|completed), auto_approved, assigned_by, assigned_at, approved_by, approved_at, rejection_reason, deleted_at | UNIEK constraint: UNIQUE(person_id, time_slot_id) — DB-afdwingbare conflictdetectie. time_slot_id gedenormaliseerd van shifts voor deze constraint. Soft delete: ja. INDEX: (shift_id, status), (person_id, status), (person_id, time_slot_id). |
|
||||
@@ -295,6 +376,37 @@ Oplossing probleem 1 (identiteitsfragmentatie): persons krijgt user_id (nullable
|
||||
| **crowd_lists** | id (ULID), event_id, crowd_type_id, name, type (internal\|external), recipient_company_id (nullable), auto_approve (bool), max_persons (int nullable) | hasMany persons via crowd_list_persons pivot. INDEX: (event_id, type). |
|
||||
| **crowd_list_persons** | id (int AI), crowd_list_id, person_id, added_at, added_by_user_id | Nieuw pivot (oplossing probleem 4). Koppelt person aan crowd_list. UNIQUE(crowd_list_id, person_id). INDEX: (person_id). |
|
||||
|
||||
### 3.5.5a Person Tags & Vaardigheden (v1.8)
|
||||
|
||||
Tag-gebaseerd vaardigheden-/competentiesysteem voor vrijwilligers en crew. Tags worden per organisatie gedefinieerd en aan gebruikers toegewezen op organisatieniveau — persistent over events heen. Een vrijwilliger die vorig jaar als Tapper is geregistreerd, behoudt deze tag bij het volgende festival.
|
||||
|
||||
| **Tabel** | **Belangrijkste kolommen** | **Relaties, constraints & opmerkingen** |
|
||||
|----|----|----|
|
||||
| **person_tags** | id (ULID), organisation_id, name (string 50), category (string 50 nullable), icon (string 50 nullable), color (hex string 7 nullable), is_active (bool), sort_order (int) | Organisatie-level tagdefinities. Bijv. "Tapper", "EHBO", "Duits". Deactivatie via `is_active = false` (geen soft delete). UNIQUE(organisation_id, name). INDEX: (organisation_id, is_active, sort_order). |
|
||||
| **user_organisation_tags** | id (int AI), user_id, organisation_id, person_tag_id, source (self_reported\|organiser_assigned), assigned_by_user_id (nullable), proficiency (beginner\|experienced\|expert, nullable), notes (text nullable), assigned_at | Koppelt user aan tag binnen een organisatie. Twee bronnen coëxisteren voor dezelfde tag: self_reported (vrijwilliger kiest bij registratie) en organiser_assigned (coördinator kent toe op basis van ervaring). Sync endpoint vervangt alleen de opgegeven bron. UNIQUE(user_id, organisation_id, person_tag_id, source). INDEX: (user_id, organisation_id), (person_tag_id), (organisation_id, person_tag_id, proficiency). |
|
||||
|
||||
PersonResource wordt verrijkt met tags wanneer `user_id` is ingevuld. Filter endpoints: `?tag={id}` (enkelvoudig) en `?tags=id1,id2` (AND-logica: moet alle tags hebben).
|
||||
|
||||
### 3.5.5b Identiteitsmatching: Person ↔ User (v1.8 — ontwerp)
|
||||
|
||||
Enterprise-grade identiteitsresolutie met drie stappen: detectie → suggestie → bevestiging. Er vindt nooit stilzwijgend automatische koppeling plaats.
|
||||
|
||||
**Detectie-triggers:**
|
||||
- Wanneer een person wordt aangemaakt met een e-mailadres dat overeenkomt met een bestaande user → systeem maakt een pending match aan in `person_identity_matches`
|
||||
- Wanneer een nieuw user account wordt aangemaakt → systeem detecteert alle ongekoppelde persons met hetzelfde e-mailadres en maakt pending matches aan
|
||||
|
||||
**Workflow:**
|
||||
- Organisator reviewt matches: bevestigen (koppelt `person.user_id`) of afwijzen (wordt niet meer getoond)
|
||||
- Volledige audit trail: wie heeft wanneer bevestigd/afgewezen
|
||||
- Bulk-bevestigingsendpoint voor efficiëntie
|
||||
- Pending matches worden inline getoond op PersonResource en als aandachtspunten op het dashboard
|
||||
|
||||
| **Tabel** | **Belangrijkste kolommen** | **Relaties, constraints & opmerkingen** |
|
||||
|----|----|----|
|
||||
| **person_identity_matches** | id (ULID), person_id, user_id, match_type (email), confidence (high\|medium), status (pending\|confirmed\|dismissed), confirmed_by_user_id (nullable), confirmed_at (timestamp nullable), dismissed_by_user_id (nullable), dismissed_at (timestamp nullable), created_at | Respecteert UNIQUE(event_id, user_id) op persons — geen dubbele koppeling mogelijk. INDEX: (person_id, status), (user_id, status), (status). |
|
||||
|
||||
**Status:** Ontwerp afgerond, nog niet geïmplementeerd. Tabel wordt aangemaakt bij bouw van de identity matching module.
|
||||
|
||||
### 3.5.6 Accreditatie Engine
|
||||
|
||||
Oplossing probleem 5: event_accreditation_items activeert org-level items per event. Accreditatie-items zijn nu org-level geconfigureerd en per event geactiveerd met event-specifieke limieten.
|
||||
@@ -401,6 +513,13 @@ Het event dashboard is de landing page voor elke event-context. Het past zijn pr
|
||||
|
||||
Festival Sections zijn de operationele eenheden binnen een evenement. Elke sectie heeft een eigen tabblad-gebaseerde interface met: Dashboard, Shifts, Scheduler, Crew, Volunteers, Info Section, Accreditation.
|
||||
|
||||
> **Festival context:** Secties bestaan op twee niveaus. Op een festival parent event:
|
||||
> `standard` secties zijn festival-only operationele secties (bijv. Terreinploeg, Bouwploeg);
|
||||
> `cross_event` secties verschijnen overal (bijv. Backstage, EHBO, Security).
|
||||
> Op sub-events zijn alle secties `standard` en programma-specifiek (bijv. Bar Schirmbar).
|
||||
> Bij het ophalen van secties voor een sub-event worden de `cross_event` secties van het
|
||||
> parent festival automatisch meegeleverd.
|
||||
|
||||
### Sectie dashboard (per section):
|
||||
|
||||
- Filled Shifts teller: X/Y shifts gevuld. Opgesplitst Crew en Volunteers.
|
||||
@@ -425,6 +544,14 @@ Festival Sections zijn de operationele eenheden binnen een evenement. Elke secti
|
||||
|
||||
Dit is een van de meest onderscheidende architecturale keuzes van Crewli, gebaseerd op de Crescat aanpak zoals geanalyseerd uit de screenshots.
|
||||
|
||||
> **Festival context:** Time slots bestaan op twee niveaus. Op een festival parent event
|
||||
> worden time slots gebruikt voor operationele planning (opbouw, afbraak, transitie).
|
||||
> Op sub-events worden time slots gebruikt voor programma-specifieke planning (showdagen).
|
||||
> Festival-level time slots worden **niet** getoond bij het opvragen van time slots voor
|
||||
> een sub-event — elk niveau heeft zijn eigen tijdvensters. Het Event model biedt
|
||||
> `getAllRelevantTimeSlots()` om voor planningsdoeleinden alle relevante time slots over
|
||||
> niveaus heen op te halen.
|
||||
|
||||
| | |
|
||||
|:--:|----|
|
||||
| **HET MODEL IN EEN ZIN** | Time Slots = het WAN-NEER (event-niveau, per persoonscategorie). Shifts = het WAT+WAAR+HOEVEEL (sectie-niveau, met capaciteit). Vrijwilligers kiezen Time Slots. Organisator maakt Shifts per Time Slot per sectie. |
|
||||
@@ -633,6 +760,10 @@ Op de dag van het festival heeft niemand tijd voor complexe navigatie. Show Day
|
||||
|
||||
- Access Zones: Backstage, VIP, Main Stage, Production, Parking — zone_code voor scanners.
|
||||
|
||||
| | |
|
||||
|:--:|---|
|
||||
| **ARCH-07 BESLUIT (v1.8)** | Accreditatie-templates worden gebouwd als integraal onderdeel van de accreditatie-module, niet als separate toevoeging. Templates zijn de PRIMAIRE toewijzingsmethode; handmatige per-persoon toewijzing is de uitzondering. Een template definieert: voor crowd_type X werkend in sectie Y op dag Z, automatisch deze accreditatie-items voorstellen. Zie BACKLOG ARCH-07. |
|
||||
|
||||
### Check-in modal (Mission Control):
|
||||
|
||||
- Zoek op naam of scan barcode.
|
||||
@@ -758,11 +889,13 @@ REST API, WebSockets, externe koppelingen
|
||||
|----|----|
|
||||
| Auth | POST /auth/login, POST /auth/logout, POST /auth/refresh |
|
||||
| Organisations | GET\|POST /organisations, GET\|PUT\|DELETE /organisations/{id} |
|
||||
| Events | GET\|POST /organisations/{org}/events, PUT /events/{id}/status |
|
||||
| Events | GET\|POST /organisations/{org}/events, GET\|PUT /organisations/{org}/events/{event}, GET /organisations/{org}/events/{event}/children, POST /organisations/{org}/events/{event}/transition (v1.8 — status state machine). Query params: ?type=festival\|series\|event, ?include_children=true |
|
||||
| Festival Sections | GET\|POST /events/{event}/sections, GET /sections/{id}/dashboard |
|
||||
| Time Slots | GET\|POST /events/{event}/time-slots, PUT\|DELETE /time-slots/{id} |
|
||||
| Shifts | GET\|POST /sections/{section}/shifts, PUT /shifts/{id}, POST /shifts/{id}/assign |
|
||||
| Persons | GET\|POST /events/{event}/persons, POST /persons/{id}/approve, POST /persons/{id}/checkin |
|
||||
| Section Categories | GET /organisations/{org}/section-categories (v1.8 — autocomplete) |
|
||||
| Person Tags | GET\|POST\|PUT\|DELETE /organisations/{org}/person-tags, GET /organisations/{org}/person-tag-categories, GET\|POST\|PUT\|DELETE /organisations/{org}/users/{user}/tags (v1.8) |
|
||||
| Persons | GET\|POST /events/{event}/persons, POST /persons/{id}/approve, POST /persons/{id}/checkin. Filter: ?tag={id}, ?tags=id1,id2 (v1.8) |
|
||||
| Artists | GET\|POST /events/{event}/artists, GET /artists/{id}/advance-portal |
|
||||
| Advance Sections | GET\|POST /artists/{id}/sections, POST /sections/{id}/submit |
|
||||
| Volunteers | GET /events/{event}/volunteers, GET /volunteers/portal/{token} |
|
||||
@@ -850,11 +983,15 @@ Scheiding tussen de interne beheerapplicatie (app/) en externe portals (portal/)
|
||||
|
||||
Externe portals hebben event-branding (logo, kleuren) maar bieden een vereenvoudigde, rol-beperkte weergave. Ze zijn PWA-geschikt voor mobiel gebruik.
|
||||
|
||||
## 6.5 Inline validatie met 'Missing Items' blok
|
||||
## 6.5 Autocomplete-gedrag in dialogen (v1.8)
|
||||
|
||||
Alle niet-contactgegevens formuliervelden in dialogen gebruiken `autocomplete="one-time-code"` om browser-autofill interferentie te voorkomen. Persoon-dialoog contactvelden (naam, e-mail, telefoon) behouden bewust browser autocomplete — dit versnelt het invullen van bekende contactgegevens.
|
||||
|
||||
## 6.6 Inline validatie met 'Missing Items' blok
|
||||
|
||||
Formuliervalidatie sectie-voor-sectie. Toon een 'Missing Items' samenvatting onderin elke sectie die persistente zichtbaarheid heeft totdat alle vereiste velden zijn ingevuld. Geen fouten alleen bij submit.
|
||||
|
||||
## 6.6 Kaartintegratie in crew-documenten
|
||||
## 6.7 Kaartintegratie in crew-documenten
|
||||
|
||||
Per locatie/shift is een adres, coördinaten en kaartpreview (Google Maps of Leaflet embed) configureerbaar. De kaart wordt opgenomen in de server-side gegenereerde allocatiesheet PDF. Dit is essentieel voor grote outdoor festivallocaties.
|
||||
|
||||
@@ -1109,6 +1246,8 @@ Alle termen gebruikt in dit document en de codebase
|
||||
| Crowd List | Gestructureerde lijst van personen. Intern (org-gevuld) of extern (partner-gevuld). |
|
||||
| E-ticket | Digitaal ticket gegenereerd voor goedgekeurde personen met accreditatiebarcodes. |
|
||||
| Event Health | Dashboard widget die configuratieproblemen en openstaande acties toont als klikbare items. |
|
||||
| Event type model | Universeel event model (v1.7) met drie modi: flat event, festival (met programmaonderdelen) en serie (met edities). Zie sectie 3.4.1. |
|
||||
| Festival | Een event van type 'festival' dat programmaonderdelen (sub-events) bevat als container. Secties en shifts zitten op sub-events. |
|
||||
| Festival Section | Operationele eenheid binnen een event: Bar, Hospitality, Technical, Security, etc. |
|
||||
| Fill rate | Percentage gevulde slots ten opzichte van het totaal voor een shift of accreditatieitem. |
|
||||
| For Sale (Crescat) | Aantal shift-slots claimbaar via vrijwilligersportaal. In Crewli: 'slots_open_for_claiming'. |
|
||||
@@ -1119,12 +1258,15 @@ Alle termen gebruikt in dit document en de codebase
|
||||
| Mission Control | Real-time operationele hub op show-dag voor check-in en tracking. |
|
||||
| Open for Claiming | Aantal shift-slots dat zichtbaar en claimbaar is in het vrijwilligersportaal. |
|
||||
| Organisation | Een klant/organisatie die Crewli gebruikt. Bevat meerdere evenementen. |
|
||||
| Overkoepelende sectie | Festival section met type 'cross_event' die actief is over alle programmaonderdelen van een festival (bijv. EHBO, Security). |
|
||||
| Person | Individuele deelnemer gekoppeld aan een event via een Crowd Type. |
|
||||
| Production Request | Digitaal intakeformulier gestuurd naar leveranciers voor het verzamelen van logistieke vereisten. |
|
||||
| Programmaonderdeel | Sub-event binnen een festival of serie. Benaming configureerbaar: Dag, Programmaonderdeel, Editie, Locatie, Ronde. |
|
||||
| Rider | Lijst van technische en hospitality vereisten van een artiest. |
|
||||
| RSVP | Bevestigingsstroom waarbij deelnemers een uitnodiging accepteren of weigeren. |
|
||||
| Scanner | Geconfigureerd scanstation (browser of hardware) gekoppeld aan specifieke crowd types/zones. |
|
||||
| Segment | Onderverdeling van een crowd type voor granulaire communicatie en rapportage. |
|
||||
| Serie | Een event van type 'series' voor terugkerende events (edities/rondes). Zelfde parent-child structuur als festival. |
|
||||
| Shift | Sectie-specifieke toewijzing binnen een Time Slot. Koppelt sectie + tijdvenster + capaciteit. |
|
||||
| Slot (capacity) | Het aantal personen dat een Shift kan bevatten. |
|
||||
| Stage | Podiumnaam binnen een event. Heeft performances op specifieke dagen. |
|
||||
@@ -1146,4 +1288,4 @@ Alle termen gebruikt in dit document en de codebase
|
||||
|
||||
Crewli Product Design & Technical Specification — Vertrouwelijk
|
||||
|
||||
Versie 1.3 — Maart 2026 — Gebaseerd op analyse van In2Event, Crescat & WeezCrew
|
||||
Versie 1.8 — April 2026 — Gebaseerd op analyse van In2Event, Crescat & WeezCrew
|
||||
@@ -253,9 +253,9 @@ PHP 8.2 + Laravel 12 | TypeScript + Vue 3 + Vuexy/Vuetify | Pinia + TanStack Que
|
||||
- PHPUnit Feature Test per controller, minimaal: 200 + 401 + 403
|
||||
```
|
||||
|
||||
### 3.3 docs/SCHEMA.md — Levend schema-document
|
||||
### 3.3 dev-docs/SCHEMA.md — Levend schema-document
|
||||
|
||||
Maak een Markdown bestand aan in /docs/ dat de tabel-definitie bevat als platte tekst. Claude Code gebruikt dit als primaire referentie bij het genereren van migraties.
|
||||
Maak een Markdown bestand aan in /dev-docs/ dat de tabel-definitie bevat als platte tekst. Claude Code gebruikt dit als primaire referentie bij het genereren van migraties.
|
||||
|
||||
```
|
||||
# Crewli Database Schema
|
||||
@@ -409,7 +409,7 @@ Gebruik deze prompts letterlijk of als basis. De meest effectieve prompts zijn:
|
||||
|
||||
> **Module genereren — Shifts als voorbeeld**
|
||||
>
|
||||
> Lees /CLAUDE.md en /docs/SCHEMA.md voor de shifts tabel definitie.
|
||||
> Lees /CLAUDE.md en /dev-docs/SCHEMA.md voor de shifts tabel definitie.
|
||||
>
|
||||
> Bouw het volledige Shifts module in de volgorde uit CLAUDE.md sectie 'Volgorde bij elke nieuwe module'.
|
||||
>
|
||||
@@ -427,7 +427,7 @@ Gebruik deze prompts letterlijk of als basis. De meest effectieve prompts zijn:
|
||||
|
||||
> **Migration genereren**
|
||||
>
|
||||
> Genereer een Laravel migratie voor de tabel [TABELNAAM] op basis van /docs/SCHEMA.md.
|
||||
> Genereer een Laravel migratie voor de tabel [TABELNAAM] op basis van /dev-docs/SCHEMA.md.
|
||||
>
|
||||
> Gebruik $table->ulid('id')->primary() als PK.
|
||||
>
|
||||
@@ -560,7 +560,7 @@ Gebruik deze prompts letterlijk of als basis. De meest effectieve prompts zijn:
|
||||
>
|
||||
> Backend (in volgorde):
|
||||
>
|
||||
> 1. Migrations voor alle tabellen uit /docs/SCHEMA.md sectie [X.X]
|
||||
> 1. Migrations voor alle tabellen uit /dev-docs/SCHEMA.md sectie [X.X]
|
||||
> 2. Models met alle relaties, scopes en accessors
|
||||
> 3. Factories
|
||||
> 4. Policies
|
||||
@@ -668,13 +668,13 @@ Claude Code heeft een beperkt context window. Bij grote taken verliest het de co
|
||||
|
||||
- Begin elke nieuwe sessie met: 'Lees /CLAUDE.md voor je begint.'
|
||||
|
||||
- Verwijs expliciet naar relevante bestanden: 'Zie /docs/SCHEMA.md voor de tabel definitie.'
|
||||
- Verwijs expliciet naar relevante bestanden: 'Zie /dev-docs/SCHEMA.md voor de tabel definitie.'
|
||||
|
||||
- Splits grote modules: 'Bouw eerst alleen de backend (stappen 1-9). Stop dan.'
|
||||
|
||||
- Na een context-verlies: geef een samenvatting: 'We bouwen Crewli. Tot nu toe klaar: auth, organisations, events. Nu: Shifts module backend.'
|
||||
|
||||
- Gebruik /docs/API.md als levend document — houd bij wat er al gebouwd is.
|
||||
- Gebruik /dev-docs/API.md als levend document — houd bij wat er al gebouwd is.
|
||||
|
||||
## 7. Tips, Valkuilen & Best Practices
|
||||
|
||||
@@ -338,7 +338,7 @@ Je bent klaar om de eerste module te laten genereren
|
||||
- ☐ **Plak deze prompt als eerste bericht:**
|
||||
|
||||
```
|
||||
Lees eerst volledig /CLAUDE.md en /docs/SCHEMA.md.
|
||||
Lees eerst volledig /CLAUDE.md en /dev-docs/SCHEMA.md.
|
||||
|
||||
Bouw daarna Fase 1 — Foundation — in deze volgorde:
|
||||
|
||||
26
docs/.templates/concept-page.md
Normal file
26
docs/.templates/concept-page.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: [Concept naam]
|
||||
description: [Eén zin uitleg van het concept]
|
||||
tags: []
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
## In het kort
|
||||
|
||||
[2-3 zinnen die het concept uitleggen alsof je het aan iemand zonder technische kennis vertelt.]
|
||||
|
||||
## Hoe past dit in Crewli?
|
||||
|
||||
[Beschrijf de context: waar kom je dit concept tegen, en waarom is het belangrijk?]
|
||||
|
||||
## Voorbeeld
|
||||
|
||||
[Concreet voorbeeld uit de praktijk van festivalbeheer.]
|
||||
|
||||
## Gerelateerde concepten
|
||||
|
||||
- [Concept A](/guide/concepts#...)
|
||||
- [Concept B](/guide/concepts#...)
|
||||
41
docs/.templates/feature-page.md
Normal file
41
docs/.templates/feature-page.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: [Feature naam]
|
||||
description: [Eén zin die beschrijft wat de feature doet]
|
||||
tags: []
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
## Wat is dit?
|
||||
|
||||
[Korte uitleg in 2-3 zinnen: wat doet deze feature en waarom is het nuttig?]
|
||||
|
||||
## Hoe werkt het?
|
||||
|
||||
### Stap 1 — [Actie]
|
||||
|
||||
[Beschrijving met eventueel een screenshot placeholder: ``]
|
||||
|
||||
### Stap 2 — [Actie]
|
||||
|
||||
[Beschrijving]
|
||||
|
||||
## Rollen en toegang
|
||||
|
||||
| Rol | Toegang |
|
||||
|-----|---------|
|
||||
| Organisatie Admin | Volledige toegang |
|
||||
| Event Manager | [Specificeer] |
|
||||
| Vrijwilliger | [Specificeer] |
|
||||
|
||||
## Veelgestelde vragen
|
||||
|
||||
### [Vraag 1]?
|
||||
|
||||
[Antwoord]
|
||||
|
||||
## Gerelateerde pagina's
|
||||
|
||||
- [Gerelateerde feature](/organizer/...)
|
||||
65
docs/.templates/style-guide.md
Normal file
65
docs/.templates/style-guide.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Crewli Documentatie Stijlgids
|
||||
|
||||
## Doelgroep
|
||||
|
||||
Crewli-documentatie is geschreven voor mensen die evenementen en festivals organiseren.
|
||||
Zij zijn praktisch ingesteld, hebben weinig tijd, en willen snel weten hoe iets werkt.
|
||||
Ga NIET uit van technische kennis.
|
||||
|
||||
## Toon en stijl
|
||||
|
||||
- Schrijf in het **Nederlands** (informeel "je/jij", niet "u")
|
||||
- Wees **direct en bondig** — geen inleidingen van drie alinea's
|
||||
- Gebruik **actieve zinnen**: "Klik op Opslaan" in plaats van "Er kan op Opslaan geklikt worden"
|
||||
- Vermijd jargon — als een term nodig is, leg hem uit of link naar de woordenlijst
|
||||
|
||||
## Vaste terminologie
|
||||
|
||||
Gebruik ALTIJD deze termen — geen synoniemen:
|
||||
|
||||
| Term in Crewli | NIET gebruiken |
|
||||
|----------------|----------------|
|
||||
| Evenement | Event (in user-facing docs) |
|
||||
| Sectie | Afdeling, area, zone (tenzij Access Zone) |
|
||||
| Dienst | Shift (in user-facing docs) |
|
||||
| Tijdslot | Tijdblok, slot |
|
||||
| Vrijwilliger | Volunteer (in user-facing docs) |
|
||||
| Persoon | Deelnemer, gast (tenzij specifieke crowd type) |
|
||||
| Crowd type | Bezoekerstype, categorie |
|
||||
| Crowd lijst | Gastenlijst (alleen als het crowd type 'guest' is) |
|
||||
| Accreditatie | Badge, pas |
|
||||
| Access zone | Toegangszone |
|
||||
| Advancing | Rider, tech spec |
|
||||
| Briefing | Instructie, info |
|
||||
| Festival serie | Festival reeks, parent event |
|
||||
|
||||
## Paginastructuur
|
||||
|
||||
1. **Titel** — wat het IS (niet wat het doet)
|
||||
2. **Eerste alinea** — wat en waarom, max 3 zinnen
|
||||
3. **Hoe werkt het** — stappen met screenshots
|
||||
4. **Rollen en toegang** — wie mag wat
|
||||
5. **FAQ** — alleen als er echte vragen zijn, geen opvulling
|
||||
6. **Gerelateerde pagina's** — links naar verwante features
|
||||
|
||||
## Screenshots
|
||||
|
||||
- Gebruik placeholders tijdens ontwikkeling: ``
|
||||
- Naamgeving: `feature-naam-stap-nummer.png` (bijv. `event-aanmaken-stap-2.png`)
|
||||
- Bewaar in een `images/` map binnen de betreffende sectie
|
||||
- Geen persoonlijke data zichtbaar in screenshots
|
||||
|
||||
## Frontmatter
|
||||
|
||||
Elke pagina MOET deze frontmatter hebben:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Paginatitel
|
||||
description: Eén zin beschrijving
|
||||
tags: [relevante, tags, voor, zoeken]
|
||||
---
|
||||
```
|
||||
|
||||
Tags gebruiken de Crewli-terminologie (zie tabel hierboven).
|
||||
Gebruik minimaal 2 tags, maximaal 6.
|
||||
162
docs/.vitepress/config.ts
Normal file
162
docs/.vitepress/config.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
|
||||
export default defineConfig({
|
||||
title: "Crewli Docs",
|
||||
description: "Documentatie voor het Crewli event management platform",
|
||||
lang: "nl-NL",
|
||||
|
||||
head: [["link", { rel: "icon", href: "/favicon.ico" }]],
|
||||
|
||||
themeConfig: {
|
||||
logo: "/logo.svg",
|
||||
|
||||
nav: [
|
||||
{ text: "Handleiding", link: "/guide/getting-started" },
|
||||
{ text: "Organisator", link: "/organizer/" },
|
||||
{ text: "Vrijwilliger", link: "/volunteer/" },
|
||||
{ text: "Portal", link: "/portal/" },
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
"/guide/": [
|
||||
{
|
||||
text: "Aan de slag",
|
||||
items: [
|
||||
{ text: "Introductie", link: "/guide/getting-started" },
|
||||
{ text: "Kernconcepten", link: "/guide/concepts" },
|
||||
{ text: "Woordenlijst", link: "/guide/glossary" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"/organizer/": [
|
||||
{
|
||||
text: "Organisatie",
|
||||
items: [
|
||||
{ text: "Overzicht", link: "/organizer/" },
|
||||
{
|
||||
text: "Organisatie inrichten",
|
||||
link: "/organizer/organisation-setup",
|
||||
},
|
||||
{ text: "Teamleden beheren", link: "/organizer/team-members" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Evenementen",
|
||||
items: [
|
||||
{ text: "Event aanmaken", link: "/organizer/events/create-event" },
|
||||
{
|
||||
text: "Festival series",
|
||||
link: "/organizer/events/festival-series",
|
||||
},
|
||||
{
|
||||
text: "Event statussen",
|
||||
link: "/organizer/events/event-statuses",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Secties & Diensten",
|
||||
items: [
|
||||
{ text: "Secties beheren", link: "/organizer/shifts/sections" },
|
||||
{ text: "Tijdslots", link: "/organizer/shifts/time-slots" },
|
||||
{ text: "Diensten plannen", link: "/organizer/shifts/planning" },
|
||||
{ text: "Toewijzingen", link: "/organizer/shifts/assignments" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Personen & Crowd",
|
||||
items: [
|
||||
{ text: "Personen beheren", link: "/organizer/persons/manage" },
|
||||
{ text: "Crowd types", link: "/organizer/persons/crowd-types" },
|
||||
{ text: "Crowd lijsten", link: "/organizer/persons/crowd-lists" },
|
||||
{ text: "Tags", link: "/organizer/persons/tags" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Accreditatie",
|
||||
items: [
|
||||
{
|
||||
text: "Accreditatie overzicht",
|
||||
link: "/organizer/accreditation/overview",
|
||||
},
|
||||
{
|
||||
text: "Access zones",
|
||||
link: "/organizer/accreditation/access-zones",
|
||||
},
|
||||
{ text: "Templates", link: "/organizer/accreditation/templates" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Artiesten",
|
||||
items: [
|
||||
{ text: "Artiesten beheren", link: "/organizer/artists/manage" },
|
||||
{
|
||||
text: "Advancing workflow",
|
||||
link: "/organizer/artists/advancing",
|
||||
},
|
||||
{ text: "Programma", link: "/organizer/artists/programme" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Communicatie",
|
||||
items: [
|
||||
{ text: "Briefings", link: "/organizer/communication/briefings" },
|
||||
{ text: "Berichten", link: "/organizer/communication/messages" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"/volunteer/": [
|
||||
{
|
||||
text: "Vrijwilliger",
|
||||
items: [
|
||||
{ text: "Overzicht", link: "/volunteer/" },
|
||||
{ text: "Registreren", link: "/volunteer/registration" },
|
||||
{ text: "Diensten kiezen", link: "/volunteer/shift-signup" },
|
||||
{ text: "Mijn paspoort", link: "/volunteer/passport" },
|
||||
{ text: "Check-in", link: "/volunteer/check-in" },
|
||||
],
|
||||
},
|
||||
],
|
||||
"/portal/": [
|
||||
{
|
||||
text: "Artiestenportaal",
|
||||
items: [
|
||||
{ text: "Overzicht", link: "/portal/" },
|
||||
{ text: "Advancing invullen", link: "/portal/artist-advancing" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Leveranciersportaal",
|
||||
items: [{ text: "Toegang", link: "/portal/supplier-access" }],
|
||||
},
|
||||
{
|
||||
text: "Persportaal",
|
||||
items: [
|
||||
{
|
||||
text: "Accreditatie aanvragen",
|
||||
link: "/portal/press-accreditation",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
search: {
|
||||
provider: "local",
|
||||
},
|
||||
|
||||
socialLinks: [
|
||||
// { icon: 'github', link: 'https://github.com/...' },
|
||||
],
|
||||
|
||||
footer: {
|
||||
message: "Crewli — Event Management Platform",
|
||||
},
|
||||
|
||||
editLink: {
|
||||
pattern:
|
||||
"https://gitea.hausmans.cloud/bert.hausmans/crewli/_edit/main/docs/:path",
|
||||
text: "Bewerk deze pagina",
|
||||
},
|
||||
},
|
||||
});
|
||||
92
docs/API.md
92
docs/API.md
@@ -1,92 +0,0 @@
|
||||
# Crewli API Contract
|
||||
|
||||
Base path: `/api/v1/`
|
||||
|
||||
Auth: Bearer token (Sanctum)
|
||||
|
||||
## Auth
|
||||
|
||||
- `POST /auth/login`
|
||||
- `POST /auth/logout`
|
||||
- `GET /auth/me`
|
||||
|
||||
## Organisations
|
||||
|
||||
- `GET /organisations` — list (super admin)
|
||||
- `POST /organisations` — create
|
||||
- `GET /organisations/{org}` — show
|
||||
- `PUT /organisations/{org}` — update
|
||||
- `GET /organisations/{org}/members` — members
|
||||
- `POST /organisations/{org}/invite` — invite user
|
||||
|
||||
## Events
|
||||
|
||||
- `GET /organisations/{org}/events`
|
||||
- `POST /organisations/{org}/events`
|
||||
- `GET /organisations/{org}/events/{event}`
|
||||
- `PUT /organisations/{org}/events/{event}`
|
||||
|
||||
## Crowd Types
|
||||
|
||||
- `GET /organisations/{org}/crowd-types`
|
||||
- `POST /organisations/{org}/crowd-types`
|
||||
- `PUT /organisations/{org}/crowd-types/{type}`
|
||||
- `DELETE /organisations/{org}/crowd-types/{type}`
|
||||
|
||||
## Companies
|
||||
|
||||
- `GET /organisations/{org}/companies`
|
||||
- `POST /organisations/{org}/companies`
|
||||
- `PUT /organisations/{org}/companies/{company}`
|
||||
- `DELETE /organisations/{org}/companies/{company}`
|
||||
|
||||
## Festival Sections
|
||||
|
||||
- `GET /events/{event}/sections`
|
||||
- `POST /events/{event}/sections`
|
||||
- `PUT /events/{event}/sections/{section}`
|
||||
- `DELETE /events/{event}/sections/{section}`
|
||||
- `POST /events/{event}/sections/reorder`
|
||||
|
||||
## Time Slots
|
||||
|
||||
- `GET /events/{event}/time-slots`
|
||||
- `POST /events/{event}/time-slots`
|
||||
- `PUT /events/{event}/time-slots/{timeSlot}`
|
||||
- `DELETE /events/{event}/time-slots/{timeSlot}`
|
||||
|
||||
## Shifts
|
||||
|
||||
- `GET /events/{event}/sections/{section}/shifts`
|
||||
- `POST /events/{event}/sections/{section}/shifts`
|
||||
- `PUT /events/{event}/sections/{section}/shifts/{shift}`
|
||||
- `DELETE /events/{event}/sections/{section}/shifts/{shift}`
|
||||
- `POST /events/{event}/sections/{section}/shifts/{shift}/assign`
|
||||
- `POST /events/{event}/sections/{section}/shifts/{shift}/claim`
|
||||
|
||||
## Persons
|
||||
|
||||
- `GET /events/{event}/persons`
|
||||
- `POST /events/{event}/persons`
|
||||
- `GET /events/{event}/persons/{person}`
|
||||
- `PUT /events/{event}/persons/{person}`
|
||||
- `POST /events/{event}/persons/{person}/approve`
|
||||
- `DELETE /events/{event}/persons/{person}`
|
||||
|
||||
## Crowd Lists
|
||||
|
||||
- `GET /events/{event}/crowd-lists`
|
||||
- `POST /events/{event}/crowd-lists`
|
||||
- `PUT /events/{event}/crowd-lists/{list}`
|
||||
- `DELETE /events/{event}/crowd-lists/{list}`
|
||||
- `POST /events/{event}/crowd-lists/{list}/persons`
|
||||
- `DELETE /events/{event}/crowd-lists/{list}/persons/{person}`
|
||||
|
||||
## Locations
|
||||
|
||||
- `GET /events/{event}/locations`
|
||||
- `POST /events/{event}/locations`
|
||||
- `PUT /events/{event}/locations/{location}`
|
||||
- `DELETE /events/{event}/locations/{location}`
|
||||
|
||||
_(Extend this contract per module as endpoints are implemented.)_
|
||||
11
docs/guide/concepts.md
Normal file
11
docs/guide/concepts.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Kernconcepten
|
||||
description: De belangrijkste begrippen en concepten binnen Crewli uitgelegd.
|
||||
tags: ['concepten', 'uitleg', 'basis']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina legt de kernconcepten van Crewli uit: organisaties, evenementen, secties, diensten, personen en meer. Begin hier als je voor het eerst met Crewli werkt.
|
||||
11
docs/guide/getting-started.md
Normal file
11
docs/guide/getting-started.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Aan de slag met Crewli
|
||||
description: Leer de basis van Crewli en begin met het beheren van je evenementen.
|
||||
tags: ['introductie', 'aan de slag', 'basis']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina helpt je op weg met je eerste stappen in Crewli: een organisatie aanmaken, je team uitnodigen, en je eerste evenement opzetten.
|
||||
11
docs/guide/glossary.md
Normal file
11
docs/guide/glossary.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Woordenlijst
|
||||
description: Alle Crewli-termen op een rij met uitleg.
|
||||
tags: ['woordenlijst', 'terminologie', 'begrippen']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Een alfabetisch overzicht van alle termen die je in Crewli tegenkomt, met een korte uitleg per begrip.
|
||||
31
docs/index.md
Normal file
31
docs/index.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
layout: home
|
||||
title: Crewli Documentatie
|
||||
description: Leer hoe je Crewli gebruikt voor je evenementen en festivals
|
||||
|
||||
hero:
|
||||
name: Crewli Docs
|
||||
text: Documentatie voor het Crewli platform
|
||||
tagline: Alles wat je nodig hebt om je evenementen en festivals te beheren
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Aan de slag
|
||||
link: /guide/getting-started
|
||||
- theme: alt
|
||||
text: Organisator handleiding
|
||||
link: /organizer/
|
||||
|
||||
features:
|
||||
- title: Evenementen beheren
|
||||
details: Maak evenementen aan, plan festival series, en beheer je volledige evenementportfolio.
|
||||
link: /organizer/events/create-event
|
||||
- title: Diensten plannen
|
||||
details: Richt secties in, maak tijdslots aan, en plan diensten voor je crew.
|
||||
link: /organizer/shifts/planning
|
||||
- title: Vrijwilligers
|
||||
details: Registratie, dienstkeuze, check-in en het vrijwilligerspaspoort.
|
||||
link: /volunteer/
|
||||
- title: Artiesten & Advancing
|
||||
details: Beheer je line-up en verzamel technische specificaties via het portaal.
|
||||
link: /organizer/artists/advancing
|
||||
---
|
||||
11
docs/organizer/accreditation/access-zones.md
Normal file
11
docs/organizer/accreditation/access-zones.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Access zones
|
||||
description: Definieer toegangszones en koppel ze aan accreditaties.
|
||||
tags: ['access zone', 'accreditatie', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je access zones instelt voor je evenement en hoe je ze koppelt aan accreditaties om de toegang te regelen.
|
||||
11
docs/organizer/accreditation/overview.md
Normal file
11
docs/organizer/accreditation/overview.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Accreditatie overzicht
|
||||
description: Beheer accreditaties voor je evenement met access zones en templates.
|
||||
tags: ['accreditatie', 'overzicht', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina geeft een overzicht van het accreditatiesysteem in Crewli: hoe je accreditaties instelt, toewijst en beheert.
|
||||
11
docs/organizer/accreditation/templates.md
Normal file
11
docs/organizer/accreditation/templates.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Accreditatie templates
|
||||
description: Maak herbruikbare templates voor accreditaties met vooraf ingestelde access zones.
|
||||
tags: ['accreditatie', 'template', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je accreditatie templates aanmaakt en gebruikt om snel accreditaties toe te wijzen met de juiste toegangsrechten.
|
||||
11
docs/organizer/artists/advancing.md
Normal file
11
docs/organizer/artists/advancing.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Advancing workflow
|
||||
description: Verzamel technische specificaties van artiesten via het advancing-formulier.
|
||||
tags: ['advancing', 'artiest', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft de advancing workflow: hoe je formulieren verstuurt naar artiesten, antwoorden bijhoudt, en technische specificaties verzamelt.
|
||||
11
docs/organizer/artists/manage.md
Normal file
11
docs/organizer/artists/manage.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Artiesten beheren
|
||||
description: Voeg artiesten toe aan je evenement en beheer hun gegevens.
|
||||
tags: ['artiest', 'beheer', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je artiesten toevoegt, hun gegevens beheert, en ze koppelt aan je programmering.
|
||||
11
docs/organizer/artists/programme.md
Normal file
11
docs/organizer/artists/programme.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Programma
|
||||
description: Stel het programma samen met artiesten, tijden en podia.
|
||||
tags: ['programma', 'artiest', 'planning', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je het programma van je evenement opbouwt: artiesten toewijzen aan podia en tijden, en het programmaoverzicht beheren.
|
||||
11
docs/organizer/communication/briefings.md
Normal file
11
docs/organizer/communication/briefings.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Briefings
|
||||
description: Stuur briefings naar je crew met alle informatie die ze nodig hebben.
|
||||
tags: ['briefing', 'communicatie', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je briefings aanmaakt, verstuurt naar specifieke groepen, en bijhoudt wie de briefing heeft gelezen.
|
||||
11
docs/organizer/communication/messages.md
Normal file
11
docs/organizer/communication/messages.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Berichten
|
||||
description: Stuur berichten naar personen en groepen binnen je evenement.
|
||||
tags: ['berichten', 'communicatie', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je berichten verstuurt, ontvangt en beheert binnen Crewli.
|
||||
11
docs/organizer/events/create-event.md
Normal file
11
docs/organizer/events/create-event.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Evenement aanmaken
|
||||
description: Maak een nieuw evenement aan binnen je organisatie.
|
||||
tags: ['evenement', 'aanmaken', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft stap voor stap hoe je een nieuw evenement aanmaakt, het type kiest, en de basisgegevens invult.
|
||||
11
docs/organizer/events/event-statuses.md
Normal file
11
docs/organizer/events/event-statuses.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Evenement statussen
|
||||
description: Begrijp de verschillende statussen van een evenement en wat ze betekenen.
|
||||
tags: ['evenement', 'status', 'workflow', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft de levenscyclus van een evenement: van concept tot afgerond, en welke acties bij elke status horen.
|
||||
11
docs/organizer/events/festival-series.md
Normal file
11
docs/organizer/events/festival-series.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Festival series
|
||||
description: Beheer terugkerende festivals als een serie met gedeelde instellingen.
|
||||
tags: ['festival serie', 'evenement', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe festival series werken: hoe je een parent-evenement aanmaakt, edities toevoegt, en instellingen deelt tussen edities.
|
||||
11
docs/organizer/index.md
Normal file
11
docs/organizer/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Organisator overzicht
|
||||
description: Alles wat je als organisator moet weten over het beheren van evenementen in Crewli.
|
||||
tags: ['organisator', 'overzicht', 'beheer']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Als organisator beheer je evenementen, secties, diensten, personen en communicatie. Dit overzicht helpt je navigeren naar de juiste handleiding.
|
||||
11
docs/organizer/organisation-setup.md
Normal file
11
docs/organizer/organisation-setup.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Organisatie inrichten
|
||||
description: Stel je organisatie in met naam, logo en basisinstellingen.
|
||||
tags: ['organisatie', 'instellingen', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je je organisatie inricht in Crewli: basisgegevens instellen, logo uploaden, en je organisatie klaar maken voor gebruik.
|
||||
11
docs/organizer/persons/crowd-lists.md
Normal file
11
docs/organizer/persons/crowd-lists.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Crowd lijsten
|
||||
description: Beheer lijsten van personen per crowd type voor je evenement.
|
||||
tags: ['crowd lijst', 'persoon', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je crowd lijsten bekijkt, filtert en exporteert, en hoe ze samenhangen met crowd types en accreditatie.
|
||||
11
docs/organizer/persons/crowd-types.md
Normal file
11
docs/organizer/persons/crowd-types.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Crowd types
|
||||
description: Definieer categorieën voor verschillende soorten bezoekers en deelnemers.
|
||||
tags: ['crowd type', 'persoon', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je crowd types aanmaakt en gebruikt om personen te categoriseren, zoals vrijwilligers, artiesten, pers en leveranciers.
|
||||
11
docs/organizer/persons/manage.md
Normal file
11
docs/organizer/persons/manage.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Personen beheren
|
||||
description: Voeg personen toe, bewerk gegevens en beheer je personenbestand.
|
||||
tags: ['persoon', 'beheer', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je personen toevoegt, bewerkt en verwijdert, en hoe het personenbestand samenhangt met crowd types en tags.
|
||||
11
docs/organizer/persons/tags.md
Normal file
11
docs/organizer/persons/tags.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Tags
|
||||
description: Gebruik tags om personen flexibel te labelen en te filteren.
|
||||
tags: ['tag', 'persoon', 'filter', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je tags aanmaakt, toewijst aan personen, en gebruikt om snel de juiste mensen te vinden.
|
||||
11
docs/organizer/shifts/assignments.md
Normal file
11
docs/organizer/shifts/assignments.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Toewijzingen
|
||||
description: Wijs personen toe aan diensten en beheer de dienstlijst.
|
||||
tags: ['toewijzing', 'dienst', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je personen toewijst aan diensten, hoe de wachtlijst werkt, en hoe je toewijzingen beheert.
|
||||
11
docs/organizer/shifts/planning.md
Normal file
11
docs/organizer/shifts/planning.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Diensten plannen
|
||||
description: Plan diensten door secties, tijdslots en personen aan elkaar te koppelen.
|
||||
tags: ['dienst', 'planning', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft de complete workflow voor het plannen van diensten: van het opzetten van secties tot het toewijzen van personen.
|
||||
11
docs/organizer/shifts/sections.md
Normal file
11
docs/organizer/shifts/sections.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Secties beheren
|
||||
description: Maak secties aan en organiseer je evenement in logische onderdelen.
|
||||
tags: ['sectie', 'organisator', 'beheer']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je secties aanmaakt, hernoemt, herordent en verwijdert binnen je evenement.
|
||||
11
docs/organizer/shifts/time-slots.md
Normal file
11
docs/organizer/shifts/time-slots.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Tijdslots
|
||||
description: Definieer tijdslots waarbinnen diensten gepland worden.
|
||||
tags: ['tijdslot', 'planning', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je tijdslots aanmaakt en beheert, en hoe ze samenhangen met secties en diensten.
|
||||
11
docs/organizer/team-members.md
Normal file
11
docs/organizer/team-members.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Teamleden beheren
|
||||
description: Nodig teamleden uit en beheer hun rollen binnen je organisatie.
|
||||
tags: ['teamleden', 'rollen', 'uitnodigen', 'organisator']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je teamleden uitnodigt, rollen toewijst, en toegang beheert binnen je organisatie.
|
||||
2514
docs/package-lock.json
generated
Normal file
2514
docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
docs/package.json
Normal file
13
docs/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "crewli-docs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs:dev": "vitepress dev",
|
||||
"docs:build": "vitepress build",
|
||||
"docs:preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.4"
|
||||
}
|
||||
}
|
||||
11
docs/portal/artist-advancing.md
Normal file
11
docs/portal/artist-advancing.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Advancing invullen
|
||||
description: Vul als artiest je technische specificaties en wensen in via het portaal.
|
||||
tags: ['portal', 'advancing', 'artiest']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je als artiest het advancing-formulier invult: technische specificaties, cateringwensen, en overige informatie.
|
||||
11
docs/portal/index.md
Normal file
11
docs/portal/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Portal overzicht
|
||||
description: Toegang tot Crewli voor artiesten, leveranciers en pers via het externe portaal.
|
||||
tags: ['portal', 'overzicht', 'extern']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Het Crewli portaal biedt externe partijen — artiesten, leveranciers en pers — toegang tot de informatie en formulieren die ze nodig hebben.
|
||||
11
docs/portal/press-accreditation.md
Normal file
11
docs/portal/press-accreditation.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Persaccreditatie aanvragen
|
||||
description: Vraag als persfotograaf of journalist accreditatie aan voor een evenement.
|
||||
tags: ['portal', 'pers', 'accreditatie']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe pers accreditatie aanvraagt via het portaal, welke gegevens nodig zijn, en hoe de beoordeling werkt.
|
||||
11
docs/portal/supplier-access.md
Normal file
11
docs/portal/supplier-access.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Leverancierstoegang
|
||||
description: Krijg als leverancier toegang tot relevante evenementinformatie via het portaal.
|
||||
tags: ['portal', 'leverancier', 'toegang']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe leveranciers toegang krijgen tot het portaal en welke informatie er beschikbaar is.
|
||||
11
docs/volunteer/check-in.md
Normal file
11
docs/volunteer/check-in.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Check-in
|
||||
description: Check in bij je dienst met je QR-code of handmatig.
|
||||
tags: ['vrijwilliger', 'check-in', 'dienst']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe de check-in werkt: je QR-code tonen, handmatig inchecken, en wat er gebeurt als je incheckt.
|
||||
11
docs/volunteer/index.md
Normal file
11
docs/volunteer/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Vrijwilliger overzicht
|
||||
description: Alles wat je als vrijwilliger moet weten over het gebruik van Crewli.
|
||||
tags: ['vrijwilliger', 'overzicht']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Als vrijwilliger gebruik je Crewli om je te registreren voor een evenement, diensten te kiezen, je paspoort te bekijken en in te checken.
|
||||
11
docs/volunteer/passport.md
Normal file
11
docs/volunteer/passport.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Mijn paspoort
|
||||
description: Bekijk je persoonlijke vrijwilligerspaspoort met je diensten en accreditatie.
|
||||
tags: ['vrijwilliger', 'paspoort', 'accreditatie']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft het vrijwilligerspaspoort: wat je er vindt, hoe je het gebruikt, en hoe het samenhangt met je diensten en accreditatie.
|
||||
11
docs/volunteer/registration.md
Normal file
11
docs/volunteer/registration.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Registreren
|
||||
description: Meld je aan als vrijwilliger voor een evenement.
|
||||
tags: ['vrijwilliger', 'registratie', 'aanmelden']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je je registreert als vrijwilliger, je profiel invult, en toegang krijgt tot het evenement.
|
||||
11
docs/volunteer/shift-signup.md
Normal file
11
docs/volunteer/shift-signup.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Diensten kiezen
|
||||
description: Bekijk beschikbare diensten en schrijf je in.
|
||||
tags: ['vrijwilliger', 'dienst', 'inschrijven']
|
||||
---
|
||||
|
||||
# {{ $frontmatter.title }}
|
||||
|
||||
{{ $frontmatter.description }}
|
||||
|
||||
Deze pagina beschrijft hoe je beschikbare diensten bekijkt, je voorkeuren aangeeft, en je inschrijft voor diensten.
|
||||
@@ -0,0 +1,119 @@
|
||||
# De beste E2E-teststack voor Vue 3 + Laravel in 2026
|
||||
|
||||
**Playwright met Pest 4 vormt de optimale, gratis testcombinatie voor een Vue 3 + Laravel SaaS-applicatie.** Playwright domineert inmiddels de markt met **37 miljoen wekelijkse npm-downloads** (5× meer dan Cypress), terwijl Laravel zelf Pest 4 — dat Playwright onder de motorkap gebruikt — officieel aanbeveelt boven Laravel Dusk. Voor een startup die snel en goedkoop wil testen, levert deze combinatie cross-browser testing, gratis parallelle uitvoering en diepe Laravel-integratie op zonder een cent aan toollicenties. AI-gestuurde tools zoals Playwright MCP voegen daar gratis testgeneratie aan toe, terwijl commerciële opties pas bij groei relevant worden.
|
||||
|
||||
## Playwright wint de vergelijking op alle fronten
|
||||
|
||||
De strijd tussen Playwright en Cypress is in 2025-2026 beslecht. Playwright passeerde Cypress in juni 2024 qua npm-downloads en het verschil groeit exponentieel. In de State of JS 2024-enquête scoorde Playwright hoger op zowel **tevredenheid als gebruiksgroei**, en circa 50% van QA-professionals gebruikt nu Playwright tegenover 14% voor Cypress.
|
||||
|
||||
De architecturale voordelen zijn structureel. Playwright draait buiten de browser via het DevTools Protocol, waardoor het **multi-tab, multi-origin en cross-browser testing** (inclusief Safari/WebKit) native ondersteunt. Cypress draait in de browser zelf, wat een uitstekende debugging-ervaring oplevert maar fundamentele beperkingen met zich meebrengt. Qua snelheid toonde een BigBinary-casestudy dat een authenticatieflow van ~2 minuten in Cypress in minder dan **20 seconden** draaide in Playwright — een verbetering van 88%.
|
||||
|
||||
| Criterium | Playwright | Cypress |
|
||||
|-----------|-----------|---------|
|
||||
| **Wekelijkse downloads** | ~37M | ~7.3M |
|
||||
| **Cross-browser** | Chromium, Firefox, WebKit | Chromium, Firefox, beperkt WebKit |
|
||||
| **Parallelle uitvoering** | Gratis (sharding) | Vereist Cypress Cloud ($67+/maand) |
|
||||
| **Multi-tab/origin** | Volledig ondersteund | Beperkt door same-origin beleid |
|
||||
| **API-mocking** | `page.route()` — zeer fijnmazig | `cy.intercept()` — beperkter |
|
||||
| **Talen** | JS, TS, Python, Java, C# | Alleen JS/TS |
|
||||
| **Kosten** | **$0** | $0 basis, $67-267/maand voor Cloud |
|
||||
|
||||
Cypress behoudt één niche-voordeel: de **time-travel debugger** in de browser is ongeëvenaard voor interactieve debugging. Voor teams die developer experience boven alles prioriteren en geen Safari-testing nodig hebben, blijft Cypress verdedigbaar. De officiële Vue.js-documentatie beveelt beide tools aan voor E2E-testing, maar noemt Playwright eerst.
|
||||
|
||||
## Pest 4 vervangt Laravel Dusk als de standaard
|
||||
|
||||
De belangrijkste ontwikkeling voor Laravel-ontwikkelaars is de release van **Pest 4 in augustus 2025**. Laravel's eigen documentatie bevat nu een expliciete aanbeveling bovenaan de Dusk-pagina: *"Pest 4 now includes automated browser testing which offers significant performance and usability improvements compared to Laravel Dusk. For new projects, we recommend using Pest for browser testing."*
|
||||
|
||||
Pest 4 gebruikt Playwright onder de motorkap maar biedt een **PHP-native API**. Dit elimineert het grootste pijnpunt van een gescheiden frontend/backend teststack: context-switching tussen PHP en JavaScript. Een Pest 4 browser-test ziet er zo uit:
|
||||
|
||||
```php
|
||||
it('kan een project aanmaken via het dashboard', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
visit(route('login'))
|
||||
->fill('email', $user->email)
|
||||
->fill('password', 'password')
|
||||
->click('Login')
|
||||
->assertSee('Dashboard')
|
||||
->click('Nieuw Project')
|
||||
->fill('name', 'Mijn SaaS')
|
||||
->click('Aanmaken')
|
||||
->assertSee('Mijn SaaS');
|
||||
|
||||
$this->assertDatabaseHas('projects', ['name' => 'Mijn SaaS']);
|
||||
$this->assertAuthenticated();
|
||||
});
|
||||
```
|
||||
|
||||
De cruciale voordelen ten opzichte van Dusk zijn: **RefreshDatabase werkt gewoon** in browser-tests (bij Dusk onmogelijk vanwege het aparte HTTP-proces), multi-browser support via `--browser`, ingebouwde **visuele regressie-testing**, test-sharding voor CI, en een unified test suite waar unit-, feature- en browser-tests samenleven. De installatie is simpel: `composer require pestphp/pest-plugin-browser --dev` gevolgd door `npx playwright install`.
|
||||
|
||||
Laravel Dusk (v8.3.6) blijft onderhouden en werkt met Laravel 11-13, maar is voor nieuwe projecten niet langer de aangeraden keuze. Bestaande Dusk-tests kunnen geleidelijk gemigreerd worden — Pest is gebouwd op PHPUnit, dus alles blijft compatibel.
|
||||
|
||||
## Vuetify-componenten vereisen specifieke teststrategieën
|
||||
|
||||
Het testen van Vuetify-componenten met E2E-tools kent **bekende uitdagingen** die je vooraf moet kennen. Het meest gedocumenteerde probleem betreft `v-select`: Vuetify's interne `<div class="v-field__input">` onderschept click-events, waardoor Playwright's standaard `.click()` faalt. Een Vuetify-maintainer bevestigde dit als een architecturaal kenmerk, niet een bug.
|
||||
|
||||
De betrouwbaarste workaround is het klikken op het dropdown-icoon in plaats van het select-element zelf:
|
||||
|
||||
```typescript
|
||||
// Stabiele helper voor Vuetify v-select
|
||||
async function vuetifySelect(page: Page, testId: string, optionText: string) {
|
||||
await page.locator(`[data-testid="${testId}"] i`).click();
|
||||
await page.getByRole('option', { name: optionText }).click();
|
||||
}
|
||||
```
|
||||
|
||||
Vergelijkbare patronen gelden voor `v-dialog` (rendered in een overlay buiten de component-tree), `v-autocomplete` (vereist eerst typen, dan selecteren uit dynamische dropdown) en `v-data-table` checkboxes (nested child-elements). Vuetify propageert **geen `data-testid` naar interne sub-elementen** — een feature request hiervoor staat al jaren open.
|
||||
|
||||
Voor een Vuexy-template specifiek geldt: er wordt geen testinfrastructuur meegeleverd. Bouw een Page Object Model voor de standaard Vuexy-layouts (sidebar, app bar, data tables) en voeg systematisch `data-testid`-attributen toe aan je aangepaste templates. Gebruik `data-testid` als primaire selectorstrategie, `getByRole` als secundair, en vermijd class-based selectors volledig — die breken bij Vuetify-updates.
|
||||
|
||||
## AI-testtools: gratis mogelijkheden versus dure platforms
|
||||
|
||||
Het AI-testlandschap in 2026 is sterk gesegmenteerd tussen gratis open-source opties en dure enterprise-platforms. Voor een startup is de **Playwright MCP-server** (Model Context Protocol) de meest relevante innovatie: een gratis, open-source brug tussen AI-modellen (Claude, GitHub Copilot, Cursor) en Playwright's browser-automatisering. MCP gebruikt de **accessibility tree** van een pagina om betrouwbaar met UI te interacteren en kan tests genereren vanuit natuurlijke taal.
|
||||
|
||||
| Tool | Type | Kosten | Geschiktheid startup |
|
||||
|------|------|--------|---------------------|
|
||||
| **Playwright MCP** | Open-source AI-brug | Gratis (+ LLM API-kosten) | ★★★★★ Uitstekend |
|
||||
| **BugBug** | Low-code recorder | Gratis tier (onbeperkt), Pro €189/maand | ★★★★★ Uitstekend |
|
||||
| **Katalon Studio** | All-in-one platform | Gratis tier, Premium €67/seat/maand | ★★★★ Goed |
|
||||
| **Testim (Tricentis)** | AI smart locators | Gratis community tier, enterprise op aanvraag | ★★★ Matig |
|
||||
| **Autify** | No-code AI | Starter €99/maand | ★★★ Matig |
|
||||
| **mabl** | AI-native platform | Vanaf ~€499/maand | ★★ Duur |
|
||||
| **Applitools** | Visuele AI | Vanaf ~€969/maand | ★★ Duur |
|
||||
| **QA Wolf** | Managed dienst | ~€7.500/maand | ★ Onbetaalbaar |
|
||||
|
||||
**BugBug** verdient speciale aandacht voor startups: de gratis tier biedt onbeperkte tests, onbeperkte lokale runs en onbeperkte gebruikers. De Pro-versie (€189/maand) voegt cloud runs en CI/CD-integratie toe. Het is geoptimaliseerd voor Chromium en mist geavanceerde AI self-healing, maar de eenvoud is een pluspunt.
|
||||
|
||||
Voor visuele regressie-testing: begin met Playwright's ingebouwde `toHaveScreenshot()` (gratis). Wordt dit onvoldoende door false positives, stap dan over naar **Percy** (5.000 gratis screenshots/maand). Applitools is pas relevant bij complexe UI's op schaal.
|
||||
|
||||
## GitHub Actions: een betaalbare CI/CD-pipeline
|
||||
|
||||
Playwright integreert naadloos met GitHub Actions en biedt **gratis parallelle uitvoering** via sharding — het grootste kostenvoordeel ten opzichte van Cypress, dat hiervoor een Cloud-abonnement vereist. Een complete pipeline voor Vue 3 + Laravel ziet er als volgt uit:
|
||||
|
||||
**Fase 1** (parallel): Backend-tests met Pest/PHPUnit + Frontend unit-tests met Vitest. **Fase 2** (na succes): E2E browser-tests met Playwright, geshard over 4 runners. Dit fail-fast patroon voorkomt dat dure browser-tests draaien wanneer basis-tests falen.
|
||||
|
||||
De kosten zijn minimaal. GitHub biedt **2.000 gratis minuten per maand** op het Free-plan (3.000 op Pro). Een typische E2E-suite met 4 shards kost ~€0,16 per run op Linux. Bij 20 runs per werkdag komt dit neer op **€28-70 per maand** — en voor publieke repositories zijn alle standaard-runners gratis. Gebruik Playwright's Docker-image (`mcr.microsoft.com/playwright:v1.58.2-noble`) om ~30 seconden browserinstallatie per run te besparen, en cache `node_modules` tussen runs.
|
||||
|
||||
```yaml
|
||||
# Playwright sharding in GitHub Actions
|
||||
strategy:
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
steps:
|
||||
- run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
```
|
||||
|
||||
## De aanbevolen teststack voor een startup
|
||||
|
||||
De optimale, kostenefficiënte testpiramide voor een Vue 3 + Laravel SaaS-applicatie in 2026 bestaat uit drie lagen. **60-70% backend**: Pest/PHPUnit feature-tests voor API-endpoints, models, services en policies — met `RefreshDatabase` en factories. **15-20% frontend**: Vitest + Vue Test Utils voor component-tests van Vue 3-componenten, Pinia stores en composables. **10-15% E2E**: Pest 4 browser-testing (Playwright) voor kritieke gebruikersflows zoals login, registratie, betaling en onboarding.
|
||||
|
||||
Het concrete advies hangt af van je team:
|
||||
|
||||
- **PHP-first team** → Pest 4 met browser-plugin. Eén taal, één test suite, maximale Laravel-integratie. Officieel aanbevolen door Laravel.
|
||||
- **JavaScript-first team** → Standalone Playwright met `@hyvor/laravel-playwright` voor Laravel-integratie (artisan commands, factories, database-operaties vanuit tests).
|
||||
- **Bestaand Dusk-project** → Houd werkende Dusk-tests, schrijf nieuwe tests in Pest 4, migreer flakey tests eerst.
|
||||
|
||||
Voeg **Playwright MCP** toe aan je IDE (Cursor, VS Code met Copilot) voor AI-gestuurde testgeneratie vanuit natuurlijke taal — dit is gratis en versnelt het schrijven van tests aanzienlijk. Gebruik `data-testid`-attributen overal in je Vuetify/Vuexy-templates en bouw herbruikbare helper-functies voor lastige Vuetify-componenten zoals `v-select` en `v-autocomplete`.
|
||||
|
||||
**Totale maandelijkse kosten: €0-189.** Playwright, Pest 4, Vitest en GitHub Actions (binnen gratis limiet) kosten niets. Alleen BugBug Pro (optioneel, voor niet-technische teamleden) of extra GitHub Actions-minuten bij intensief gebruik genereren kosten. Geen enkele commerciële AI-tool is noodzakelijk voor een startup — de open-source stack is in 2026 compleet genoeg om professioneel te testen.
|
||||
Reference in New Issue
Block a user