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:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { RegistrationFormField } from '@/types/registration-form-field'
|
||||
import type { NormalizedOption } from '@/types/registration-form-field'
|
||||
import { FIELD_TYPE_LABELS } from '@/types/registration-field-template'
|
||||
|
||||
defineProps<{
|
||||
@@ -11,10 +12,11 @@ defineEmits<{
|
||||
delete: [field: RegistrationFormField]
|
||||
}>()
|
||||
|
||||
function formatOptions(options: string[] | null): string {
|
||||
function formatOptions(options: NormalizedOption[] | null): string {
|
||||
if (!options?.length) return ''
|
||||
if (options.length <= 5) return options.join(', ')
|
||||
return `${options.slice(0, 5).join(', ')}, +${options.length - 5} meer`
|
||||
const labels = options.map(o => o.label)
|
||||
if (labels.length <= 5) return labels.join(', ')
|
||||
return `${labels.slice(0, 5).join(', ')}, +${labels.length - 5} meer`
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -59,10 +61,10 @@ function formatOptions(options: string[] | null): string {
|
||||
|
||||
<!-- Options preview -->
|
||||
<div
|
||||
v-if="field.options?.length"
|
||||
v-if="field.normalized_options?.length"
|
||||
class="text-body-2 text-medium-emphasis mb-1"
|
||||
>
|
||||
Opties: {{ formatOptions(field.options) }}
|
||||
Opties: {{ formatOptions(field.normalized_options) }}
|
||||
</div>
|
||||
|
||||
<!-- Tag category -->
|
||||
@@ -83,6 +85,14 @@ function formatOptions(options: string[] | null): string {
|
||||
|
||||
<!-- Status badges -->
|
||||
<div class="d-flex flex-wrap gap-1 mt-2">
|
||||
<VChip
|
||||
v-if="field.display_width === 'half'"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="secondary"
|
||||
>
|
||||
Halve breedte
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="field.is_required"
|
||||
size="x-small"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { VForm } from 'vuetify/components/VForm'
|
||||
import { usePersonTagCategories } from '@/composables/api/usePersonTags'
|
||||
import { requiredValidator } from '@core/utils/validators'
|
||||
import type { RegistrationFormField, RegistrationFormFieldCreateDTO } from '@/types/registration-form-field'
|
||||
import type { RegistrationFieldType } from '@/types/registration-field-template'
|
||||
import type { RegistrationFieldType, FieldDisplayWidth } from '@/types/registration-field-template'
|
||||
import { FIELD_TYPE_LABELS, FIELD_TYPES_WITH_OPTIONS } from '@/types/registration-field-template'
|
||||
import type { AxiosError } from 'axios'
|
||||
import type { ApiErrorResponse } from '@/types/auth'
|
||||
@@ -29,10 +29,15 @@ const fieldTypeOptions = Object.entries(FIELD_TYPE_LABELS).map(([value, title])
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const refVForm = ref<VForm>()
|
||||
|
||||
interface OptionEntry {
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const defaultForm = () => ({
|
||||
label: '',
|
||||
field_type: 'text' as string,
|
||||
options: [] as string[],
|
||||
options: [] as OptionEntry[],
|
||||
tag_category: null as string | null,
|
||||
is_required: false,
|
||||
is_filterable: false,
|
||||
@@ -40,6 +45,7 @@ const defaultForm = () => ({
|
||||
is_admin_only: false,
|
||||
section: '',
|
||||
help_text: '',
|
||||
display_width: 'full' as FieldDisplayWidth,
|
||||
})
|
||||
|
||||
const form = ref(defaultForm())
|
||||
@@ -61,7 +67,9 @@ watch(modelValue, (open) => {
|
||||
form.value = {
|
||||
label: props.field.label,
|
||||
field_type: props.field.field_type,
|
||||
options: props.field.options ? [...props.field.options] : [],
|
||||
options: props.field.normalized_options
|
||||
? props.field.normalized_options.map(o => ({ label: o.label, description: o.description ?? '' }))
|
||||
: [],
|
||||
tag_category: props.field.tag_category,
|
||||
is_required: props.field.is_required,
|
||||
is_filterable: props.field.is_filterable,
|
||||
@@ -69,6 +77,7 @@ watch(modelValue, (open) => {
|
||||
is_admin_only: props.field.is_admin_only,
|
||||
section: props.field.section ?? '',
|
||||
help_text: props.field.help_text ?? '',
|
||||
display_width: props.field.display_width ?? 'full',
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -78,7 +87,7 @@ watch(modelValue, (open) => {
|
||||
})
|
||||
|
||||
function addOption() {
|
||||
form.value.options.push('')
|
||||
form.value.options.push({ label: '', description: '' })
|
||||
}
|
||||
|
||||
function removeOption(index: number) {
|
||||
@@ -98,6 +107,7 @@ function onSubmit() {
|
||||
is_admin_only: form.value.is_admin_only,
|
||||
section: form.value.section || null,
|
||||
help_text: form.value.help_text || null,
|
||||
display_width: form.value.display_width,
|
||||
}
|
||||
|
||||
if (!props.field) {
|
||||
@@ -105,7 +115,12 @@ function onSubmit() {
|
||||
}
|
||||
|
||||
if (showOptions.value) {
|
||||
payload.options = form.value.options.filter(o => o.trim() !== '')
|
||||
payload.options = form.value.options
|
||||
.filter(o => o.label.trim() !== '')
|
||||
.map(o => o.description.trim()
|
||||
? { label: o.label, description: o.description }
|
||||
: { label: o.label },
|
||||
)
|
||||
}
|
||||
else {
|
||||
payload.options = null
|
||||
@@ -180,23 +195,45 @@ defineExpose({ setErrors })
|
||||
>
|
||||
<label class="text-body-2 mb-2 d-block">Opties</label>
|
||||
<div
|
||||
v-for="(_, index) in form.options"
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="d-flex align-center gap-x-2 mb-2"
|
||||
class="mb-3"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.options[index]"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-x"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-3 position-relative"
|
||||
>
|
||||
<VRow dense>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
label="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
placeholder="Korte toelichting die onder de optie verschijnt"
|
||||
hint="Max 200 tekens"
|
||||
counter="200"
|
||||
maxlength="200"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VBtn
|
||||
icon="tabler-x"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
class="position-absolute"
|
||||
style="inset-block-start: 4px; inset-inline-end: 4px"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</VCard>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
@@ -243,7 +280,39 @@ defineExpose({ setErrors })
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
/>
|
||||
>
|
||||
<label class="text-body-2 text-medium-emphasis d-block mb-1">
|
||||
Veldbreedte
|
||||
</label>
|
||||
<VBtnToggle
|
||||
v-model="form.display_width"
|
||||
mandatory
|
||||
density="compact"
|
||||
>
|
||||
<VBtn
|
||||
value="full"
|
||||
size="small"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-layout-distribute-horizontal"
|
||||
size="16"
|
||||
class="me-1"
|
||||
/>
|
||||
Volledig
|
||||
</VBtn>
|
||||
<VBtn
|
||||
value="half"
|
||||
size="small"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-layout-columns"
|
||||
size="16"
|
||||
class="me-1"
|
||||
/>
|
||||
Half
|
||||
</VBtn>
|
||||
</VBtnToggle>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppTextarea
|
||||
v-model="form.help_text"
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@/composables/api/useRegistrationFieldTemplates'
|
||||
import { usePersonTagCategories } from '@/composables/api/usePersonTags'
|
||||
import { requiredValidator } from '@core/utils/validators'
|
||||
import type { RegistrationFieldTemplate, RegistrationFieldTemplateCreateDTO, RegistrationFieldType } from '@/types/registration-field-template'
|
||||
import type { RegistrationFieldTemplate, RegistrationFieldTemplateCreateDTO, RegistrationFieldType, FieldDisplayWidth } from '@/types/registration-field-template'
|
||||
import { FIELD_TYPE_LABELS, FIELD_TYPES_WITH_OPTIONS } from '@/types/registration-field-template'
|
||||
import type { AxiosError } from 'axios'
|
||||
import type { ApiErrorResponse } from '@/types/auth'
|
||||
@@ -46,10 +46,15 @@ const refVForm = ref<VForm>()
|
||||
const showSuccess = ref(false)
|
||||
const successMessage = ref('')
|
||||
|
||||
interface OptionEntry {
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const defaultForm = () => ({
|
||||
label: '',
|
||||
field_type: 'text' as string,
|
||||
options: [] as string[],
|
||||
options: [] as OptionEntry[],
|
||||
tag_category: null as string | null,
|
||||
is_required: false,
|
||||
is_filterable: false,
|
||||
@@ -58,6 +63,7 @@ const defaultForm = () => ({
|
||||
section: '',
|
||||
help_text: '',
|
||||
sort_order: 0,
|
||||
display_width: 'full' as FieldDisplayWidth,
|
||||
})
|
||||
|
||||
const form = ref(defaultForm())
|
||||
@@ -90,7 +96,9 @@ function openEditDialog(template: RegistrationFieldTemplate) {
|
||||
form.value = {
|
||||
label: template.label,
|
||||
field_type: template.field_type,
|
||||
options: template.options ? [...template.options] : [],
|
||||
options: template.normalized_options
|
||||
? template.normalized_options.map(o => ({ label: o.label, description: o.description ?? '' }))
|
||||
: [],
|
||||
tag_category: template.tag_category,
|
||||
is_required: template.is_required,
|
||||
is_filterable: template.is_filterable,
|
||||
@@ -99,13 +107,14 @@ function openEditDialog(template: RegistrationFieldTemplate) {
|
||||
section: template.section ?? '',
|
||||
help_text: template.help_text ?? '',
|
||||
sort_order: template.sort_order,
|
||||
display_width: template.display_width ?? 'full',
|
||||
}
|
||||
errors.value = {}
|
||||
isDialogOpen.value = true
|
||||
}
|
||||
|
||||
function addOption() {
|
||||
form.value.options.push('')
|
||||
form.value.options.push({ label: '', description: '' })
|
||||
}
|
||||
|
||||
function removeOption(index: number) {
|
||||
@@ -126,6 +135,7 @@ function onSubmit() {
|
||||
section: form.value.section || null,
|
||||
help_text: form.value.help_text || null,
|
||||
sort_order: form.value.sort_order,
|
||||
display_width: form.value.display_width,
|
||||
}
|
||||
|
||||
if (!editingTemplate.value) {
|
||||
@@ -133,7 +143,12 @@ function onSubmit() {
|
||||
}
|
||||
|
||||
if (showOptions.value) {
|
||||
payload.options = form.value.options.filter(o => o.trim() !== '')
|
||||
payload.options = form.value.options
|
||||
.filter(o => o.label.trim() !== '')
|
||||
.map(o => o.description.trim()
|
||||
? { label: o.label, description: o.description }
|
||||
: { label: o.label },
|
||||
)
|
||||
}
|
||||
else {
|
||||
payload.options = null
|
||||
@@ -414,23 +429,45 @@ function activate(template: RegistrationFieldTemplate) {
|
||||
>
|
||||
<label class="text-body-2 mb-2 d-block">Opties</label>
|
||||
<div
|
||||
v-for="(_, index) in form.options"
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="d-flex align-center gap-x-2 mb-2"
|
||||
class="mb-3"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.options[index]"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-x"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-3 position-relative"
|
||||
>
|
||||
<VRow dense>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
label="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
placeholder="Korte toelichting die onder de optie verschijnt"
|
||||
hint="Max 200 tekens"
|
||||
counter="200"
|
||||
maxlength="200"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VBtn
|
||||
icon="tabler-x"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
class="position-absolute"
|
||||
style="inset-block-start: 4px; inset-inline-end: 4px"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</VCard>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
@@ -485,6 +522,42 @@ function activate(template: RegistrationFieldTemplate) {
|
||||
:error-messages="errors.sort_order"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<label class="text-body-2 text-medium-emphasis d-block mb-1">
|
||||
Veldbreedte
|
||||
</label>
|
||||
<VBtnToggle
|
||||
v-model="form.display_width"
|
||||
mandatory
|
||||
density="compact"
|
||||
>
|
||||
<VBtn
|
||||
value="full"
|
||||
size="small"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-layout-distribute-horizontal"
|
||||
size="16"
|
||||
class="me-1"
|
||||
/>
|
||||
Volledig
|
||||
</VBtn>
|
||||
<VBtn
|
||||
value="half"
|
||||
size="small"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-layout-columns"
|
||||
size="16"
|
||||
class="me-1"
|
||||
/>
|
||||
Half
|
||||
</VBtn>
|
||||
</VBtnToggle>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppTextarea
|
||||
v-model="form.help_text"
|
||||
|
||||
@@ -31,12 +31,22 @@ export const FIELD_TYPES_WITH_OPTIONS: RegistrationFieldType[] = [
|
||||
'checkbox',
|
||||
]
|
||||
|
||||
export type FieldDisplayWidth = 'full' | 'half'
|
||||
|
||||
export interface NormalizedOption {
|
||||
label: string
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export type FieldOption = string | { label: string; description?: string | null }
|
||||
|
||||
export interface RegistrationFieldTemplate {
|
||||
id: string
|
||||
label: string
|
||||
slug: string
|
||||
field_type: RegistrationFieldType
|
||||
options: string[] | null
|
||||
options: FieldOption[] | null
|
||||
normalized_options: NormalizedOption[] | null
|
||||
tag_category: string | null
|
||||
is_required: boolean
|
||||
is_filterable: boolean
|
||||
@@ -45,6 +55,7 @@ export interface RegistrationFieldTemplate {
|
||||
section: string | null
|
||||
help_text: string | null
|
||||
sort_order: number
|
||||
display_width: FieldDisplayWidth
|
||||
is_system: boolean
|
||||
is_active: boolean
|
||||
}
|
||||
@@ -52,7 +63,7 @@ export interface RegistrationFieldTemplate {
|
||||
export interface RegistrationFieldTemplateCreateDTO {
|
||||
label: string
|
||||
field_type: RegistrationFieldType
|
||||
options?: string[] | null
|
||||
options?: FieldOption[] | null
|
||||
tag_category?: string | null
|
||||
is_required?: boolean
|
||||
is_filterable?: boolean
|
||||
@@ -61,6 +72,7 @@ export interface RegistrationFieldTemplateCreateDTO {
|
||||
section?: string | null
|
||||
help_text?: string | null
|
||||
sort_order?: number
|
||||
display_width?: FieldDisplayWidth
|
||||
}
|
||||
|
||||
export interface RegistrationFieldTemplateUpdateDTO extends Partial<Omit<RegistrationFieldTemplateCreateDTO, 'field_type'>> {
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import type { RegistrationFieldType } from './registration-field-template'
|
||||
|
||||
export type FieldDisplayWidth = 'full' | 'half'
|
||||
|
||||
export interface NormalizedOption {
|
||||
label: string
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export type FieldOption = string | { label: string; description?: string | null }
|
||||
|
||||
export interface RegistrationFormField {
|
||||
id: string
|
||||
event_id: string
|
||||
label: string
|
||||
slug: string
|
||||
field_type: RegistrationFieldType
|
||||
options: string[] | null
|
||||
options: FieldOption[] | null
|
||||
normalized_options: NormalizedOption[] | null
|
||||
tag_category: string | null
|
||||
is_required: boolean
|
||||
is_portal_visible: boolean
|
||||
@@ -15,6 +25,7 @@ export interface RegistrationFormField {
|
||||
section: string | null
|
||||
help_text: string | null
|
||||
sort_order: number
|
||||
display_width: FieldDisplayWidth
|
||||
created_at: string
|
||||
updated_at: string
|
||||
available_tags?: Array<{ id: string; name: string; category: string | null }>
|
||||
@@ -23,7 +34,7 @@ export interface RegistrationFormField {
|
||||
export interface RegistrationFormFieldCreateDTO {
|
||||
label: string
|
||||
field_type: RegistrationFieldType
|
||||
options?: string[] | null
|
||||
options?: FieldOption[] | null
|
||||
tag_category?: string | null
|
||||
is_required?: boolean
|
||||
is_portal_visible?: boolean
|
||||
@@ -32,6 +43,7 @@ export interface RegistrationFormFieldCreateDTO {
|
||||
section?: string | null
|
||||
help_text?: string | null
|
||||
sort_order?: number
|
||||
display_width?: FieldDisplayWidth
|
||||
}
|
||||
|
||||
export interface RegistrationFormFieldUpdateDTO extends Partial<Omit<RegistrationFormFieldCreateDTO, 'field_type'>> {}
|
||||
|
||||
@@ -1063,7 +1063,7 @@ async function onSubmit() {
|
||||
v-for="field in group.fields"
|
||||
:key="field.id"
|
||||
cols="12"
|
||||
:md="field.field_type === 'textarea' || field.field_type === 'checkbox' ? '12' : '6'"
|
||||
:md="field.display_width === 'half' ? 6 : 12"
|
||||
>
|
||||
<div :id="`reg-field-${field.slug}`">
|
||||
<VTextField
|
||||
@@ -1112,13 +1112,26 @@ async function onSubmit() {
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:items="field.options ?? []"
|
||||
:items="field.normalized_options ?? []"
|
||||
item-title="label"
|
||||
item-value="label"
|
||||
:label="field.label + (field.is_required ? ' *' : '')"
|
||||
:hint="field.help_text ?? undefined"
|
||||
persistent-hint
|
||||
:error-messages="fieldErrors[field.slug]"
|
||||
:clearable="!field.is_required"
|
||||
/>
|
||||
>
|
||||
<template #item="{ props: itemProps, item }">
|
||||
<VListItem v-bind="itemProps">
|
||||
<template
|
||||
v-if="item.raw.description"
|
||||
#subtitle
|
||||
>
|
||||
{{ item.raw.description }}
|
||||
</template>
|
||||
</VListItem>
|
||||
</template>
|
||||
</VSelect>
|
||||
|
||||
<VSelect
|
||||
v-else-if="field.field_type === 'multiselect'"
|
||||
@@ -1129,12 +1142,25 @@ async function onSubmit() {
|
||||
multiple
|
||||
chips
|
||||
closable-chips
|
||||
:items="field.options ?? []"
|
||||
:items="field.normalized_options ?? []"
|
||||
item-title="label"
|
||||
item-value="label"
|
||||
:label="field.label + (field.is_required ? ' *' : '')"
|
||||
:hint="field.help_text ?? undefined"
|
||||
persistent-hint
|
||||
:error-messages="fieldErrors[field.slug]"
|
||||
/>
|
||||
>
|
||||
<template #item="{ props: itemProps, item }">
|
||||
<VListItem v-bind="itemProps">
|
||||
<template
|
||||
v-if="item.raw.description"
|
||||
#subtitle
|
||||
>
|
||||
{{ item.raw.description }}
|
||||
</template>
|
||||
</VListItem>
|
||||
</template>
|
||||
</VSelect>
|
||||
|
||||
<div v-else-if="field.field_type === 'checkbox'">
|
||||
<div class="text-body-2 d-block mb-1 text-high-emphasis">
|
||||
@@ -1147,14 +1173,25 @@ async function onSubmit() {
|
||||
{{ field.help_text }}
|
||||
</p>
|
||||
<VCheckbox
|
||||
v-for="opt in (field.options ?? [])"
|
||||
:key="opt"
|
||||
:model-value="isCheckboxChecked(field.slug, opt)"
|
||||
v-for="opt in (field.normalized_options ?? [])"
|
||||
:key="opt.label"
|
||||
:model-value="isCheckboxChecked(field.slug, opt.label)"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
:label="opt"
|
||||
@update:model-value="(v: boolean | null) => toggleCheckboxOption(field.slug, opt, v)"
|
||||
/>
|
||||
@update:model-value="(v: boolean | null) => toggleCheckboxOption(field.slug, opt.label, v)"
|
||||
>
|
||||
<template #label>
|
||||
<div>
|
||||
<span class="text-body-1">{{ opt.label }}</span>
|
||||
<p
|
||||
v-if="opt.description"
|
||||
class="text-body-2 text-medium-emphasis mt-1 mb-0"
|
||||
>
|
||||
{{ opt.description }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</VCheckbox>
|
||||
<div
|
||||
v-if="fieldErrors[field.slug]"
|
||||
class="text-caption text-error"
|
||||
@@ -1180,13 +1217,24 @@ async function onSubmit() {
|
||||
:error-messages="fieldErrors[field.slug]"
|
||||
>
|
||||
<VRadio
|
||||
v-for="opt in (field.options ?? [])"
|
||||
:key="opt"
|
||||
:label="opt"
|
||||
:value="opt"
|
||||
v-for="opt in (field.normalized_options ?? [])"
|
||||
:key="opt.label"
|
||||
:value="opt.label"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
/>
|
||||
>
|
||||
<template #label>
|
||||
<div>
|
||||
<span class="text-body-1">{{ opt.label }}</span>
|
||||
<p
|
||||
v-if="opt.description"
|
||||
class="text-body-2 text-medium-emphasis mt-1 mb-0"
|
||||
>
|
||||
{{ opt.description }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</VRadio>
|
||||
</VRadioGroup>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,22 +9,31 @@ export type RegistrationFieldType =
|
||||
| 'number'
|
||||
| 'tag_picker'
|
||||
|
||||
export type FieldDisplayWidth = 'full' | 'half'
|
||||
|
||||
export interface RegistrationTagOption {
|
||||
id: string
|
||||
name: string
|
||||
category: string | null
|
||||
}
|
||||
|
||||
export interface NormalizedOption {
|
||||
label: string
|
||||
description: string | null
|
||||
}
|
||||
|
||||
export interface RegistrationField {
|
||||
id: string
|
||||
label: string
|
||||
slug: string
|
||||
field_type: RegistrationFieldType
|
||||
options: string[] | null
|
||||
options: (string | { label: string; description?: string | null })[] | null
|
||||
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