feat: HEADING field type for registration forms — replace section property with structural field
Replace the per-field `section` text property with a dedicated HEADING field type that organizers add as a separate block for visual grouping. Also fixes duplicate heading bug on portal radio fields, replaces cramped VBtnToggle with VSelect for field width, and adds grouped field type dropdown with structure/input categories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -173,20 +173,6 @@ const currentStepKind = computed(() => {
|
||||
|
||||
const registrationFieldsList = computed(() => registrationData.value?.registration_fields ?? [])
|
||||
|
||||
const groupedRegistrationFields = computed(() => {
|
||||
const fields = registrationFieldsList.value
|
||||
const groups: { section: string | null; fields: RegistrationField[] }[] = []
|
||||
for (const f of fields) {
|
||||
const last = groups[groups.length - 1]
|
||||
if (last && last.section === f.section)
|
||||
last.fields.push(f)
|
||||
else
|
||||
groups.push({ section: f.section, fields: [f] })
|
||||
}
|
||||
|
||||
return groups
|
||||
})
|
||||
|
||||
function defaultFieldValue(type: RegistrationFieldType): unknown {
|
||||
switch (type) {
|
||||
case 'multiselect':
|
||||
@@ -196,6 +182,7 @@ function defaultFieldValue(type: RegistrationFieldType): unknown {
|
||||
case 'boolean':
|
||||
return false
|
||||
case 'number':
|
||||
case 'heading':
|
||||
return null
|
||||
default:
|
||||
return ''
|
||||
@@ -204,6 +191,7 @@ function defaultFieldValue(type: RegistrationFieldType): unknown {
|
||||
|
||||
watch(registrationFieldsList, fields => {
|
||||
for (const f of fields) {
|
||||
if (f.field_type === 'heading') continue
|
||||
if (!(f.slug in fieldFormData.value))
|
||||
fieldFormData.value[f.slug] = defaultFieldValue(f.field_type)
|
||||
}
|
||||
@@ -337,7 +325,7 @@ function validateDynamicFields(): boolean {
|
||||
fieldErrors.value = {}
|
||||
let firstErrorSlug: string | null = null
|
||||
for (const field of registrationFieldsList.value) {
|
||||
if (!field.is_required) continue
|
||||
if (field.field_type === 'heading' || !field.is_required) continue
|
||||
const val = fieldFormData.value[field.slug]
|
||||
if (isEmptyFieldValue(val, field.field_type)) {
|
||||
fieldErrors.value[field.slug] = 'Dit veld is verplicht.'
|
||||
@@ -434,6 +422,7 @@ function formatTimeRange(start: string, end: string): string {
|
||||
function buildFieldValuesPayload(): Record<string, unknown> | undefined {
|
||||
const out: Record<string, unknown> = {}
|
||||
for (const field of registrationFieldsList.value) {
|
||||
if (field.field_type === 'heading') continue
|
||||
const val = fieldFormData.value[field.slug]
|
||||
if (field.field_type === 'boolean') {
|
||||
if (typeof val === 'boolean')
|
||||
@@ -1047,21 +1036,32 @@ async function onSubmit() {
|
||||
|
||||
<!-- Step 1: Extra informatie (dynamic registration_fields) -->
|
||||
<div v-show="currentStep === 1">
|
||||
<template
|
||||
v-for="(group, gi) in groupedRegistrationFields"
|
||||
:key="gi"
|
||||
>
|
||||
<div
|
||||
v-if="group.section"
|
||||
class="text-subtitle-2 text-medium-emphasis mt-4 mb-2"
|
||||
>
|
||||
{{ group.section }}
|
||||
</div>
|
||||
|
||||
<VRow>
|
||||
<VCol
|
||||
v-for="field in group.fields"
|
||||
<template
|
||||
v-for="(field, fieldIndex) in registrationFieldsList"
|
||||
:key="field.id"
|
||||
>
|
||||
<!-- HEADING field: full-width section header -->
|
||||
<VCol
|
||||
v-if="field.field_type === 'heading'"
|
||||
cols="12"
|
||||
:class="fieldIndex === 0 ? '' : 'mt-4'"
|
||||
>
|
||||
<div class="text-subtitle-1 font-weight-medium mb-1">
|
||||
{{ field.label }}
|
||||
</div>
|
||||
<div
|
||||
v-if="field.help_text"
|
||||
class="text-body-2 text-medium-emphasis mb-3"
|
||||
>
|
||||
{{ field.help_text }}
|
||||
</div>
|
||||
<VDivider class="mb-2" />
|
||||
</VCol>
|
||||
|
||||
<!-- Regular input field -->
|
||||
<VCol
|
||||
v-else
|
||||
cols="12"
|
||||
:md="field.display_width === 'half' ? 6 : 12"
|
||||
>
|
||||
@@ -1270,8 +1270,8 @@ async function onSubmit() {
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
</template>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<p
|
||||
v-if="registrationFieldsList.length === 0"
|
||||
|
||||
@@ -8,6 +8,7 @@ export type RegistrationFieldType =
|
||||
| 'boolean'
|
||||
| 'number'
|
||||
| 'tag_picker'
|
||||
| 'heading'
|
||||
|
||||
export type FieldDisplayWidth = 'full' | 'half'
|
||||
|
||||
@@ -31,7 +32,6 @@ export interface RegistrationField {
|
||||
normalized_options: NormalizedOption[] | null
|
||||
tag_category: string | null
|
||||
is_required: boolean
|
||||
section: string | null
|
||||
help_text: string | null
|
||||
display_width: FieldDisplayWidth
|
||||
available_tags?: RegistrationTagOption[]
|
||||
|
||||
Reference in New Issue
Block a user