feat(app): registration fields management page in event settings

Adds a new settings sub-page for managing dynamic registration form fields
per event. Includes sortable field list, create/edit dialog, template picker,
and import-from-event functionality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 23:44:14 +02:00
parent c43a922641
commit a9dcee0fc7
10 changed files with 1376 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
<script setup lang="ts">
import { useRegistrationFieldTemplates } from '@/composables/api/useRegistrationFieldTemplates'
import { useCreateFieldFromTemplate } from '@/composables/api/useRegistrationFormFields'
import { FIELD_TYPE_LABELS } from '@/types/registration-field-template'
import type { RegistrationFormField } from '@/types/registration-form-field'
const props = defineProps<{
orgId: string
eventId: string
existingFields: RegistrationFormField[]
}>()
const emit = defineEmits<{
added: []
}>()
const modelValue = defineModel<boolean>({ default: false })
const orgIdRef = computed(() => props.orgId)
const eventIdRef = computed(() => props.eventId)
const { data: templates, isLoading } = useRegistrationFieldTemplates(orgIdRef)
const { mutate: createFromTemplate, isPending } = useCreateFieldFromTemplate(eventIdRef)
const existingSlugs = computed(() =>
new Set(props.existingFields.map(f => f.slug)),
)
const activeTemplates = computed(() =>
(templates.value ?? []).filter(t => t.is_active),
)
const addingTemplateId = ref<string | null>(null)
function isAlreadyAdded(slug: string): boolean {
return existingSlugs.value.has(slug)
}
function onAdd(templateId: string) {
addingTemplateId.value = templateId
createFromTemplate(templateId, {
onSuccess: () => {
addingTemplateId.value = null
emit('added')
},
onError: () => {
addingTemplateId.value = null
},
})
}
</script>
<template>
<VDialog
v-model="modelValue"
max-width="600"
>
<VCard title="Veld uit template toevoegen">
<VCardText>
<VSkeletonLoader
v-if="isLoading"
type="list-item@3"
/>
<template v-else-if="activeTemplates.length">
<VList density="compact">
<VListItem
v-for="template in activeTemplates"
:key="template.id"
:disabled="isAlreadyAdded(template.slug)"
class="rounded mb-1"
:class="{ 'opacity-50': isAlreadyAdded(template.slug) }"
>
<template #prepend>
<VIcon
:icon="isAlreadyAdded(template.slug) ? 'tabler-check' : 'tabler-forms'"
size="20"
:color="isAlreadyAdded(template.slug) ? 'success' : undefined"
/>
</template>
<VListItemTitle>
{{ template.label }}
<span
v-if="isAlreadyAdded(template.slug)"
class="text-caption text-disabled ms-2"
>(al toegevoegd)</span>
</VListItemTitle>
<VListItemSubtitle>
{{ FIELD_TYPE_LABELS[template.field_type] ?? template.field_type }}
<template v-if="template.section">
&middot; {{ template.section }}
</template>
<template v-if="template.is_required">
&middot; Verplicht
</template>
</VListItemSubtitle>
<template #append>
<VBtn
v-if="!isAlreadyAdded(template.slug)"
size="small"
variant="tonal"
color="primary"
:loading="isPending && addingTemplateId === template.id"
:disabled="isPending"
@click="onAdd(template.id)"
>
Toevoegen
</VBtn>
</template>
</VListItem>
</VList>
</template>
<div
v-else
class="text-center pa-4"
>
<VIcon
icon="tabler-forms"
size="40"
class="mb-3 text-disabled"
/>
<p class="text-body-2 text-disabled mb-0">
Geen actieve templates beschikbaar. Maak eerst templates aan in de organisatie-instellingen.
</p>
</div>
</VCardText>
<VCardActions>
<VSpacer />
<VBtn
variant="text"
@click="modelValue = false"
>
Sluiten
</VBtn>
</VCardActions>
</VCard>
</VDialog>
</template>