refactor: remove access_requirements (toegangsbehoeften) from application

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 19:53:28 +02:00
parent b2737ba5c8
commit d06cb01726
7 changed files with 145 additions and 61 deletions

View File

@@ -40,7 +40,6 @@ final class VolunteerRegistrationRequest extends FormRequest
'tshirt_size' => ['nullable', 'string', 'in:XS,S,M,L,XL,XXL,XXXL'], 'tshirt_size' => ['nullable', 'string', 'in:XS,S,M,L,XL,XXL,XXXL'],
'first_aid' => ['nullable', 'boolean'], 'first_aid' => ['nullable', 'boolean'],
'allergies' => ['nullable', 'string', 'max:500'], 'allergies' => ['nullable', 'string', 'max:500'],
'access_requirements' => ['nullable', 'string', 'max:500'],
'driving_licence' => ['nullable', 'boolean'], 'driving_licence' => ['nullable', 'boolean'],
'motivation' => ['nullable', 'string', 'max:1000'], 'motivation' => ['nullable', 'string', 'max:1000'],

View File

@@ -59,7 +59,6 @@ final class VolunteerRegistrationService
'tshirt_size' => $validated['tshirt_size'] ?? null, 'tshirt_size' => $validated['tshirt_size'] ?? null,
'first_aid' => $validated['first_aid'] ?? false, 'first_aid' => $validated['first_aid'] ?? false,
'allergies' => $validated['allergies'] ?? null, 'allergies' => $validated['allergies'] ?? null,
'access_requirements' => $validated['access_requirements'] ?? null,
'driving_licence' => $validated['driving_licence'] ?? false, 'driving_licence' => $validated['driving_licence'] ?? false,
'motivation' => $validated['motivation'] ?? null, 'motivation' => $validated['motivation'] ?? null,
'motivation_other' => $validated['motivation_other'] ?? null, 'motivation_other' => $validated['motivation_other'] ?? null,

View File

@@ -45,7 +45,6 @@ const { errors, defineField, validateField, setFieldValue } = useForm({
tshirt_size: '', tshirt_size: '',
first_aid: false, first_aid: false,
allergies: '', allergies: '',
access_requirements: '',
driving_licence: false, driving_licence: false,
motivation: '', motivation: '',
motivation_other: '', motivation_other: '',
@@ -60,7 +59,6 @@ const [phone] = defineField('phone')
const [tshirtSize] = defineField('tshirt_size') const [tshirtSize] = defineField('tshirt_size')
const [firstAid] = defineField('first_aid') const [firstAid] = defineField('first_aid')
const [allergies] = defineField('allergies') const [allergies] = defineField('allergies')
const [accessRequirements] = defineField('access_requirements')
const [drivingLicence] = defineField('driving_licence') const [drivingLicence] = defineField('driving_licence')
const [motivation] = defineField('motivation') const [motivation] = defineField('motivation')
const [motivationOther] = defineField('motivation_other') const [motivationOther] = defineField('motivation_other')
@@ -159,11 +157,11 @@ const formattedDates = computed(() => {
}) })
// Step field mapping for validation (0-based) // Step field mapping for validation (0-based)
type FormField = 'first_name' | 'last_name' | 'date_of_birth' | 'email' | 'phone' | 'tshirt_size' | 'first_aid' | 'allergies' | 'access_requirements' | 'driving_licence' | 'motivation' | 'motivation_other' type FormField = 'first_name' | 'last_name' | 'date_of_birth' | 'email' | 'phone' | 'tshirt_size' | 'first_aid' | 'allergies' | 'driving_licence' | 'motivation' | 'motivation_other'
const stepFields: Record<number, FormField[]> = { const stepFields: Record<number, FormField[]> = {
0: ['first_name', 'last_name', 'date_of_birth', 'email', 'phone'], 0: ['first_name', 'last_name', 'date_of_birth', 'email', 'phone'],
1: ['tshirt_size', 'first_aid', 'allergies', 'access_requirements', 'driving_licence'], 1: ['tshirt_size', 'first_aid', 'allergies', 'driving_licence'],
2: ['motivation', 'motivation_other'], 2: ['motivation', 'motivation_other'],
} }
@@ -278,7 +276,6 @@ async function onSubmit() {
tshirt_size: tshirtSize.value ?? '', tshirt_size: tshirtSize.value ?? '',
first_aid: firstAid.value ?? false, first_aid: firstAid.value ?? false,
allergies: allergies.value ?? '', allergies: allergies.value ?? '',
access_requirements: accessRequirements.value ?? '',
driving_licence: drivingLicence.value ?? false, driving_licence: drivingLicence.value ?? false,
motivation: motivation.value ?? '', motivation: motivation.value ?? '',
motivation_other: motivationOther.value ?? '', motivation_other: motivationOther.value ?? '',
@@ -623,20 +620,28 @@ async function onSubmit() {
</p> </p>
</div> </div>
<!-- Step 0: Over jou --> <!-- Step 0: Over jou + contact (Vuexy: vertical fields, form separator, contact block) -->
<div v-show="currentStep === 0"> <div v-show="currentStep === 0">
<VRow> <VRow>
<VCol <VCol
cols="12" cols="12"
md="6" md="6"
> >
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-first-name"
>
Voornaam <span class="text-error">*</span>
</label>
<VTextField <VTextField
id="volunteer-reg-first-name"
v-model="firstName" v-model="firstName"
label="Voornaam *" variant="outlined"
placeholder="Je voornaam" placeholder="Je voornaam"
:error-messages="errors.first_name" :error-messages="errors.first_name"
:disabled="authStore.isAuthenticated" :disabled="authStore.isAuthenticated"
density="comfortable" density="comfortable"
hide-details="auto"
/> />
</VCol> </VCol>
@@ -644,13 +649,21 @@ async function onSubmit() {
cols="12" cols="12"
md="6" md="6"
> >
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-last-name"
>
Achternaam <span class="text-error">*</span>
</label>
<VTextField <VTextField
id="volunteer-reg-last-name"
v-model="lastName" v-model="lastName"
label="Achternaam *" variant="outlined"
placeholder="Je achternaam" placeholder="Je achternaam"
:error-messages="errors.last_name" :error-messages="errors.last_name"
:disabled="authStore.isAuthenticated" :disabled="authStore.isAuthenticated"
density="comfortable" density="comfortable"
hide-details="auto"
/> />
</VCol> </VCol>
@@ -658,40 +671,80 @@ async function onSubmit() {
cols="12" cols="12"
md="6" md="6"
> >
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-dob"
>
Geboortedatum
</label>
<VTextField <VTextField
v-model="email" id="volunteer-reg-dob"
label="E-mailadres *"
type="email"
placeholder="je@email.nl"
:error-messages="errors.email"
:disabled="authStore.isAuthenticated"
density="comfortable"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="phone"
label="Telefoonnummer"
placeholder="06 12345678"
:error-messages="errors.phone"
density="comfortable"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<VTextField
v-model="dateOfBirth" v-model="dateOfBirth"
label="Geboortedatum" class="volunteer-reg-dob-field"
variant="outlined"
type="date" type="date"
:error-messages="errors.date_of_birth" :error-messages="errors.date_of_birth"
density="comfortable" density="comfortable"
hide-details="auto"
>
<template #prepend-inner>
<VIcon
icon="tabler-calendar"
size="20"
/>
</template>
</VTextField>
</VCol>
</VRow>
<hr class="my-6 mx-n6">
<h6 class="text-h6 mb-2">
Contactgegevens
</h6>
<p class="text-body-2 text-medium-emphasis mb-6">
Vul deze gegevens zorgvuldig in: we gebruiken ze om alle informatie over het evenement naar je te versturen.
Je e-mailadres is ook je gebruikersnaam voor Crewli, het systeem waar je straks alle informatie terugvindt.
</p>
<VRow>
<VCol cols="12">
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-email"
>
E-mailadres <span class="text-error">*</span>
</label>
<VTextField
id="volunteer-reg-email"
v-model="email"
variant="outlined"
type="email"
placeholder="je@email.nl"
prepend-inner-icon="tabler-mail"
:error-messages="errors.email"
:disabled="authStore.isAuthenticated"
density="comfortable"
hide-details="auto"
/>
</VCol>
<VCol cols="12">
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-phone"
>
Telefoonnummer
</label>
<VTextField
id="volunteer-reg-phone"
v-model="phone"
variant="outlined"
placeholder="06 12345678"
prepend-inner-icon="tabler-phone"
:error-messages="errors.phone"
density="comfortable"
hide-details="auto"
/> />
</VCol> </VCol>
</VRow> </VRow>
@@ -704,21 +757,30 @@ async function onSubmit() {
cols="12" cols="12"
md="6" md="6"
> >
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-tshirt"
>
Shirtmaat
</label>
<VSelect <VSelect
id="volunteer-reg-tshirt"
v-model="tshirtSize" v-model="tshirtSize"
:items="tshirtSizeItems" :items="tshirtSizeItems"
label="Shirtmaat" variant="outlined"
placeholder="Selecteer je maat" placeholder="Selecteer je maat"
:error-messages="errors.tshirt_size" :error-messages="errors.tshirt_size"
density="comfortable" density="comfortable"
hide-details="auto"
/> />
</VCol> </VCol>
<VCol <VCol
cols="12" cols="12"
md="6" md="6"
class="d-flex align-end"
> >
<div class="d-flex ga-6 pt-2"> <div class="d-flex ga-6 flex-wrap pb-1 w-100">
<VSwitch <VSwitch
v-model="firstAid" v-model="firstAid"
label="EHBO-diploma" label="EHBO-diploma"
@@ -736,30 +798,26 @@ async function onSubmit() {
</VCol> </VCol>
<VCol cols="12"> <VCol cols="12">
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-allergies"
>
Allergieën of dieetwensen
</label>
<VTextarea <VTextarea
id="volunteer-reg-allergies"
v-model="allergies" v-model="allergies"
label="Allergieën of dieetwensen" variant="outlined"
placeholder="Laat ons weten als je allergieën of dieetwensen hebt" placeholder="Laat ons weten als je allergieën of dieetwensen hebt"
:error-messages="errors.allergies" :error-messages="errors.allergies"
:counter="500" :counter="500"
rows="2" rows="2"
auto-grow auto-grow
density="comfortable" density="comfortable"
hide-details="auto"
/> />
</VCol> </VCol>
<VCol cols="12">
<VTextarea
v-model="accessRequirements"
label="Toegangsbehoeften"
placeholder="Heb je specifieke behoeften qua toegankelijkheid?"
:error-messages="errors.access_requirements"
:counter="500"
rows="2"
auto-grow
density="comfortable"
/>
</VCol>
</VRow> </VRow>
</div> </div>
@@ -770,14 +828,22 @@ async function onSubmit() {
cols="12" cols="12"
md="6" md="6"
> >
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-motivation"
>
Waarom wil je vrijwilliger zijn?
</label>
<VSelect <VSelect
id="volunteer-reg-motivation"
v-model="motivation" v-model="motivation"
:items="motivationItems" :items="motivationItems"
label="Waarom wil je vrijwilliger zijn?" variant="outlined"
placeholder="Selecteer je motivatie" placeholder="Selecteer je motivatie"
:error-messages="errors.motivation" :error-messages="errors.motivation"
clearable clearable
density="comfortable" density="comfortable"
hide-details="auto"
/> />
</VCol> </VCol>
@@ -786,15 +852,23 @@ async function onSubmit() {
v-if="motivation" v-if="motivation"
cols="12" cols="12"
> >
<label
class="text-body-2 d-block mb-1 text-high-emphasis"
for="volunteer-reg-motivation-other"
>
Toelichting
</label>
<VTextarea <VTextarea
id="volunteer-reg-motivation-other"
v-model="motivationOther" v-model="motivationOther"
label="Toelichting" variant="outlined"
placeholder="Vertel ons meer over je motivatie..." placeholder="Vertel ons meer over je motivatie..."
:error-messages="errors.motivation_other" :error-messages="errors.motivation_other"
:counter="500" :counter="500"
rows="3" rows="3"
auto-grow auto-grow
density="comfortable" density="comfortable"
hide-details="auto"
/> />
</VCol> </VCol>
</VExpandTransition> </VExpandTransition>
@@ -1018,4 +1092,19 @@ async function onSubmit() {
z-index: 1; z-index: 1;
max-inline-size: 1000px; max-inline-size: 1000px;
} }
/*
Native date inputs have a large intrinsic min-width; without min-width: 0 the
grid cell grows and the prepend-inner icon no longer sits inside the outline
next to the value (Vuetify v-field grid: prepend-inner | field).
*/
.volunteer-reg-dob-field :deep(.v-field__field) {
min-inline-size: 0;
}
.volunteer-reg-dob-field :deep(input.v-field__input[type="date"]) {
flex: 1 1 auto;
max-inline-size: 100%;
min-inline-size: 0;
}
</style> </style>

View File

@@ -12,7 +12,6 @@ export const step2Schema = z.object({
tshirt_size: z.enum(['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL']).optional().or(z.literal('')), tshirt_size: z.enum(['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL']).optional().or(z.literal('')),
first_aid: z.boolean().default(false), first_aid: z.boolean().default(false),
allergies: z.string().max(500).optional().or(z.literal('')), allergies: z.string().max(500).optional().or(z.literal('')),
access_requirements: z.string().max(500).optional().or(z.literal('')),
driving_licence: z.boolean().default(false), driving_licence: z.boolean().default(false),
}) })

View File

@@ -51,7 +51,6 @@ export interface VolunteerRegistrationForm {
tshirt_size: string tshirt_size: string
first_aid: boolean first_aid: boolean
allergies: string allergies: string
access_requirements: string
driving_licence: boolean driving_licence: boolean
// Step 3 // Step 3
motivation: string motivation: string

View File

@@ -558,7 +558,6 @@ $effectiveDate = $shift->end_date ?? $shift->timeSlot->date;
| `first_aid` | bool | | | `first_aid` | bool | |
| `driving_licence` | bool | | | `driving_licence` | bool | |
| `allergies` | text nullable | | | `allergies` | text nullable | |
| `access_requirements` | text nullable | |
| `emergency_contact_name` | string nullable | | | `emergency_contact_name` | string nullable | |
| `emergency_contact_phone` | string nullable | | | `emergency_contact_phone` | string nullable | |
| `reliability_score` | decimal(3,2) | 0.005.00, computed via scheduled job | | `reliability_score` | decimal(3,2) | 0.005.00, computed via scheduled job |

View File

@@ -359,7 +359,7 @@ Drielaags Crescat-model. Kritieke verbetering: time_slot_id gedenormaliseerd naa
| **Tabel** | **Belangrijkste kolommen** | **Relaties, constraints & opmerkingen** | | **Tabel** | **Belangrijkste kolommen** | **Relaties, constraints & opmerkingen** |
|----|----|----| |----|----|----|
| **volunteer_profiles** | id (ULID), user_id (FK unique), bio, photo_url, tshirt_size, first_aid (bool), driving_licence (bool), allergies, access_requirements, emergency_contact_name, emergency_contact_phone, reliability_score (decimal 3,2), is_ambassador | Platform-breed, 1-op-1 met users. reliability_score 0.00-5.00, berekend via scheduled job. UNIQUE(user_id). | | **volunteer_profiles** | id (ULID), user_id (FK unique), bio, photo_url, tshirt_size, first_aid (bool), driving_licence (bool), allergies, emergency_contact_name, emergency_contact_phone, reliability_score (decimal 3,2), is_ambassador | Platform-breed, 1-op-1 met users. reliability_score 0.00-5.00, berekend via scheduled job. UNIQUE(user_id). |
| **volunteer_festival_history** | id (ULID), user_id, event_id, organisation_id, hours_planned, hours_completed, no_show_count, coordinator_rating (tinyint 1-5), coordinator_notes, would_reinvite (bool) | Per gebruiker per festival. Nooit zichtbaar voor vrijwilliger zelf. INDEX: (user_id, event_id), UNIQUE(user_id, event_id). | | **volunteer_festival_history** | id (ULID), user_id, event_id, organisation_id, hours_planned, hours_completed, no_show_count, coordinator_rating (tinyint 1-5), coordinator_notes, would_reinvite (bool) | Per gebruiker per festival. Nooit zichtbaar voor vrijwilliger zelf. INDEX: (user_id, event_id), UNIQUE(user_id, event_id). |
| **post_festival_evaluations** | id (ULID), event_id, person_id, shift_id (nullable), overall_rating (tinyint 1-5), shift_rating (tinyint 1-5), would_return (bool), feedback_text, improvement_suggestion, submitted_at, is_anonymous | Vrijwilliger evalueert na afloop. INDEX: (event_id, is_anonymous), (person_id). | | **post_festival_evaluations** | id (ULID), event_id, person_id, shift_id (nullable), overall_rating (tinyint 1-5), shift_rating (tinyint 1-5), would_return (bool), feedback_text, improvement_suggestion, submitted_at, is_anonymous | Vrijwilliger evalueert na afloop. INDEX: (event_id, is_anonymous), (person_id). |
| **festival_retrospectives** | id (ULID), event_id (unique), generated_at, volunteers_planned (int), volunteers_completed (int), no_show_count (int), no_show_pct (decimal 5,2), avg_overall_satisfaction (decimal 3,2), avg_shift_satisfaction (decimal 3,2), would_return_pct (decimal 5,2), sections_understaffed (int), sections_overstaffed (int), top_feedback (JSON: array of strings), notes (text) | Oplossing probleem 8: alle KPIs als concrete kolommen ipv JSON blob. Trendanalyse over meerdere jaren mogelijk. JSON alleen voor vrije-tekst feedback array. | | **festival_retrospectives** | id (ULID), event_id (unique), generated_at, volunteers_planned (int), volunteers_completed (int), no_show_count (int), no_show_pct (decimal 5,2), avg_overall_satisfaction (decimal 3,2), avg_shift_satisfaction (decimal 3,2), would_return_pct (decimal 5,2), sections_understaffed (int), sections_overstaffed (int), top_feedback (JSON: array of strings), notes (text) | Oplossing probleem 8: alle KPIs als concrete kolommen ipv JSON blob. Trendanalyse over meerdere jaren mogelijk. JSON alleen voor vrije-tekst feedback array. |
@@ -604,7 +604,7 @@ Vrijwilligers zijn de kern van elke festival-organisatie. Dit module ontlast de
- **Deel 1 — Over jou: Naam, e-mail, telefoon (met landcode).** - **Deel 1 — Over jou: Naam, e-mail, telefoon (met landcode).**
- **Deel 2 — Meer over jou: Shirtmaat, EHBO, allergieën, toegangsbehoeften, rijbewijs. Geconfigureerd via de formulierbouwer.** - **Deel 2 — Meer over jou: Shirtmaat, EHBO, allergieën, rijbewijs. Geconfigureerd via de formulierbouwer.**
- **Deel 3 — Motivatie: Waarom wil je vrijwilliger zijn? Dropdown + vrije tekst.** - **Deel 3 — Motivatie: Waarom wil je vrijwilliger zijn? Dropdown + vrije tekst.**