feat: registration form field display_width and option descriptions

Add configurable column widths (full/half) and optional descriptions
for radio/select/checkbox options on registration form fields.

- Migration adds display_width column to both tables
- FieldDisplayWidth enum with smart defaults per field type
- normalized_options accessor for backwards-compatible option format
- Portal form renderer uses display_width for VRow/VCol grid layout
- Radio/select/checkbox options render with descriptions
- Admin field editor supports display_width toggle and description input
- System templates updated with appropriate widths and descriptions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 07:46:36 +02:00
parent c4a23b6763
commit 9718e27029
25 changed files with 634 additions and 84 deletions

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Services;
use App\Enums\FieldDisplayWidth;
use App\Enums\RegistrationFieldType;
use App\Models\Event;
use App\Models\Organisation;
use App\Models\RegistrationFieldTemplate;
@@ -24,6 +26,11 @@ final class RegistrationFieldTemplateService
{
$data['slug'] = $this->generateUniqueSlug($organisation, $data['label']);
if (!isset($data['display_width'])) {
$fieldType = RegistrationFieldType::from($data['field_type']);
$data['display_width'] = FieldDisplayWidth::defaultForFieldType($fieldType)->value;
}
$template = $organisation->registrationFieldTemplates()->create($data);
$activityLogger = activity('registration_templates')
@@ -111,6 +118,7 @@ final class RegistrationFieldTemplateService
'section' => $template->section,
'help_text' => $template->help_text,
'sort_order' => $maxOrder + 1,
'display_width' => $template->display_width,
]);
$activityLogger = activity('registration_fields')
@@ -132,17 +140,21 @@ final class RegistrationFieldTemplateService
public static function seedSystemTemplates(Organisation $organisation): void
{
$templates = [
['label' => 'Shirtmaat', 'field_type' => 'select', 'options' => ['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL'], 'is_filterable' => true, 'sort_order' => 1],
['label' => 'Dieetwensen', 'field_type' => 'multiselect', 'options' => ['Vegetarisch', 'Veganistisch', 'Halal', 'Glutenvrij', 'Lactosevrij', 'Geen pinda\'s', 'Geen noten'], 'is_filterable' => true, 'sort_order' => 2],
['label' => 'Vergoeding', 'field_type' => 'radio', 'options' => ['Pro Deo', 'Entreeticket', 'Vrijwilligersvergoeding'], 'section' => 'Vergoeding', 'sort_order' => 3],
['label' => 'Toestemming gegevensverwerking', 'field_type' => 'boolean', 'is_required' => true, 'section' => 'Toestemming', 'help_text' => 'Ik geef toestemming voor de verwerking van mijn persoonsgegevens ten behoeve van de organisatie van dit evenement, conform de Algemene Verordening Gegevensbescherming (AVG).', 'sort_order' => 4],
['label' => 'Noodcontact naam', 'field_type' => 'text', 'section' => 'Noodcontact', 'sort_order' => 5],
['label' => 'Noodcontact telefoon', 'field_type' => 'text', 'section' => 'Noodcontact', 'sort_order' => 6],
['label' => 'EHBO / BHV diploma', 'field_type' => 'boolean', 'is_filterable' => true, 'sort_order' => 7],
['label' => 'Rijbewijs', 'field_type' => 'boolean', 'is_filterable' => true, 'sort_order' => 8],
['label' => 'Eerder vrijwilliger geweest', 'field_type' => 'boolean', 'is_filterable' => true, 'sort_order' => 9],
['label' => 'Certificaten & vaardigheden', 'field_type' => 'tag_picker', 'tag_category' => null, 'is_filterable' => true, 'sort_order' => 10],
['label' => 'Opmerkingen', 'field_type' => 'textarea', 'sort_order' => 11],
['label' => 'Shirtmaat', 'field_type' => 'select', 'options' => ['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL'], 'is_filterable' => true, 'display_width' => 'half', 'sort_order' => 1],
['label' => 'Dieetwensen', 'field_type' => 'multiselect', 'options' => ['Vegetarisch', 'Veganistisch', 'Halal', 'Glutenvrij', 'Lactosevrij', 'Geen pinda\'s', 'Geen noten'], 'is_filterable' => true, 'display_width' => 'full', 'sort_order' => 2],
['label' => 'Vergoeding', 'field_type' => 'radio', 'options' => [
['label' => 'Pro Deo', 'description' => 'Je werkt als vrijwilliger zonder financiële vergoeding'],
['label' => 'Entreeticket', 'description' => 'Je ontvangt een gratis festivalticket als dank voor je inzet'],
['label' => 'Vrijwilligersvergoeding', 'description' => 'Je ontvangt een vergoeding conform de vrijwilligersregeling'],
], 'section' => 'Vergoeding', 'display_width' => 'full', 'sort_order' => 3],
['label' => 'Toestemming gegevensverwerking', 'field_type' => 'boolean', 'is_required' => true, 'section' => 'Toestemming', 'help_text' => 'Ik geef toestemming voor de verwerking van mijn persoonsgegevens ten behoeve van de organisatie van dit evenement, conform de Algemene Verordening Gegevensbescherming (AVG).', 'display_width' => 'full', 'sort_order' => 4],
['label' => 'Noodcontact naam', 'field_type' => 'text', 'section' => 'Noodcontact', 'display_width' => 'half', 'sort_order' => 5],
['label' => 'Noodcontact telefoon', 'field_type' => 'text', 'section' => 'Noodcontact', 'display_width' => 'half', 'sort_order' => 6],
['label' => 'EHBO / BHV diploma', 'field_type' => 'boolean', 'is_filterable' => true, 'display_width' => 'half', 'sort_order' => 7],
['label' => 'Rijbewijs', 'field_type' => 'boolean', 'is_filterable' => true, 'display_width' => 'half', 'sort_order' => 8],
['label' => 'Eerder vrijwilliger geweest', 'field_type' => 'boolean', 'is_filterable' => true, 'display_width' => 'half', 'sort_order' => 9],
['label' => 'Certificaten & vaardigheden', 'field_type' => 'tag_picker', 'tag_category' => null, 'is_filterable' => true, 'display_width' => 'full', 'sort_order' => 10],
['label' => 'Opmerkingen', 'field_type' => 'textarea', 'display_width' => 'full', 'sort_order' => 11],
];
foreach ($templates as $data) {
@@ -155,6 +167,7 @@ final class RegistrationFieldTemplateService
'is_filterable' => $data['is_filterable'] ?? false,
'is_portal_visible' => true,
'is_admin_only' => false,
'display_width' => $data['display_width'] ?? 'full',
]);
}
}