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:
120
apps/app/src/components/event/ImportFromEventDialog.vue
Normal file
120
apps/app/src/components/event/ImportFromEventDialog.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import { useEventList } from '@/composables/api/useEvents'
|
||||
import { useImportFieldsFromEvent } from '@/composables/api/useRegistrationFormFields'
|
||||
|
||||
const props = defineProps<{
|
||||
orgId: string
|
||||
eventId: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
imported: [count: number, eventName: string]
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<boolean>({ default: false })
|
||||
|
||||
const orgIdRef = computed(() => props.orgId)
|
||||
const eventIdRef = computed(() => props.eventId)
|
||||
|
||||
const { data: events, isLoading: isLoadingEvents } = useEventList(orgIdRef)
|
||||
const { mutate: importFields, isPending } = useImportFieldsFromEvent(eventIdRef)
|
||||
|
||||
const selectedEventId = ref<string | null>(null)
|
||||
|
||||
const eventOptions = computed(() => {
|
||||
if (!events.value) return []
|
||||
|
||||
const options: Array<{ title: string; value: string }> = []
|
||||
|
||||
for (const event of events.value) {
|
||||
if (event.id !== props.eventId) {
|
||||
options.push({ title: event.name, value: event.id })
|
||||
}
|
||||
// Also include children for festivals
|
||||
if (event.children?.length) {
|
||||
for (const child of event.children) {
|
||||
if (child.id !== props.eventId) {
|
||||
options.push({ title: `${event.name} > ${child.name}`, value: child.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
})
|
||||
|
||||
const selectedEventName = computed(() =>
|
||||
eventOptions.value.find(e => e.value === selectedEventId.value)?.title ?? '',
|
||||
)
|
||||
|
||||
watch(modelValue, (open) => {
|
||||
if (open) {
|
||||
selectedEventId.value = null
|
||||
}
|
||||
})
|
||||
|
||||
function onImport() {
|
||||
if (!selectedEventId.value) return
|
||||
|
||||
const name = selectedEventName.value
|
||||
|
||||
importFields(selectedEventId.value, {
|
||||
onSuccess: (data) => {
|
||||
const count = Array.isArray(data) ? data.length : 0
|
||||
modelValue.value = false
|
||||
emit('imported', count, name)
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog
|
||||
v-model="modelValue"
|
||||
max-width="500"
|
||||
>
|
||||
<VCard title="Velden importeren van ander event">
|
||||
<VCardText>
|
||||
<p class="text-body-2 text-medium-emphasis mb-4">
|
||||
Kopieer alle registratievelden van een ander event naar dit event. Bestaande velden blijven behouden.
|
||||
</p>
|
||||
|
||||
<AppAutocomplete
|
||||
v-model="selectedEventId"
|
||||
label="Selecteer een event"
|
||||
:items="eventOptions"
|
||||
:loading="isLoadingEvents"
|
||||
placeholder="Zoek een event..."
|
||||
clearable
|
||||
/>
|
||||
|
||||
<VAlert
|
||||
v-if="selectedEventId"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mt-4"
|
||||
>
|
||||
De registratievelden van <strong>{{ selectedEventName }}</strong> worden gekopieerd naar het huidige event.
|
||||
</VAlert>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="modelValue = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
:loading="isPending"
|
||||
:disabled="!selectedEventId"
|
||||
@click="onImport"
|
||||
>
|
||||
Importeren
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
156
apps/app/src/components/event/RegistrationFieldCard.vue
Normal file
156
apps/app/src/components/event/RegistrationFieldCard.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<script setup lang="ts">
|
||||
import type { RegistrationFormField } from '@/types/registration-form-field'
|
||||
import { FIELD_TYPE_LABELS } from '@/types/registration-field-template'
|
||||
|
||||
defineProps<{
|
||||
field: RegistrationFormField
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
edit: [field: RegistrationFormField]
|
||||
delete: [field: RegistrationFormField]
|
||||
}>()
|
||||
|
||||
function formatOptions(options: string[] | null): string {
|
||||
if (!options?.length) return ''
|
||||
if (options.length <= 5) return options.join(', ')
|
||||
return `${options.slice(0, 5).join(', ')}, +${options.length - 5} meer`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="mb-2"
|
||||
>
|
||||
<VCardText class="pa-4">
|
||||
<div class="d-flex align-start gap-3">
|
||||
<!-- Drag handle -->
|
||||
<div class="drag-handle d-flex align-center pt-1 cursor-grab">
|
||||
<VIcon
|
||||
icon="tabler-grip-vertical"
|
||||
size="20"
|
||||
class="text-disabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-grow-1 min-width-0">
|
||||
<div class="d-flex align-center justify-space-between gap-2 mb-1">
|
||||
<span class="text-body-1 font-weight-medium">
|
||||
{{ field.label }}
|
||||
</span>
|
||||
<VChip
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="default"
|
||||
>
|
||||
{{ FIELD_TYPE_LABELS[field.field_type] ?? field.field_type }}
|
||||
</VChip>
|
||||
</div>
|
||||
|
||||
<!-- Section -->
|
||||
<div
|
||||
v-if="field.section"
|
||||
class="text-body-2 text-medium-emphasis mb-1"
|
||||
>
|
||||
Sectie: {{ field.section }}
|
||||
</div>
|
||||
|
||||
<!-- Options preview -->
|
||||
<div
|
||||
v-if="field.options?.length"
|
||||
class="text-body-2 text-medium-emphasis mb-1"
|
||||
>
|
||||
Opties: {{ formatOptions(field.options) }}
|
||||
</div>
|
||||
|
||||
<!-- Tag category -->
|
||||
<div
|
||||
v-if="field.field_type === 'tag_picker' && field.tag_category"
|
||||
class="text-body-2 text-medium-emphasis mb-1"
|
||||
>
|
||||
Categorie: {{ field.tag_category }}
|
||||
</div>
|
||||
|
||||
<!-- Help text -->
|
||||
<div
|
||||
v-if="field.help_text"
|
||||
class="text-body-2 text-disabled mb-1 text-truncate"
|
||||
>
|
||||
{{ field.help_text }}
|
||||
</div>
|
||||
|
||||
<!-- Status badges -->
|
||||
<div class="d-flex flex-wrap gap-1 mt-2">
|
||||
<VChip
|
||||
v-if="field.is_required"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
>
|
||||
Verplicht
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="field.is_filterable"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="info"
|
||||
>
|
||||
Filterbaar
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="field.is_portal_visible"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="success"
|
||||
>
|
||||
Zichtbaar
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="field.is_admin_only"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="warning"
|
||||
>
|
||||
Admin-only
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="d-flex align-center gap-x-1 pt-1">
|
||||
<VBtn
|
||||
icon="tabler-edit"
|
||||
variant="text"
|
||||
size="small"
|
||||
title="Bewerken"
|
||||
@click="$emit('edit', field)"
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
title="Verwijderen"
|
||||
@click="$emit('delete', field)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.min-width-0 {
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.cursor-grab {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.cursor-grab:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
</style>
|
||||
304
apps/app/src/components/event/RegistrationFieldFormDialog.vue
Normal file
304
apps/app/src/components/event/RegistrationFieldFormDialog.vue
Normal file
@@ -0,0 +1,304 @@
|
||||
<script setup lang="ts">
|
||||
import { VForm } from 'vuetify/components/VForm'
|
||||
import { usePersonTagCategories } from '@/composables/api/usePersonTags'
|
||||
import { requiredValidator } from '@core/utils/validators'
|
||||
import type { RegistrationFormField } from '@/types/registration-form-field'
|
||||
import { FIELD_TYPE_LABELS, FIELD_TYPES_WITH_OPTIONS } from '@/types/registration-field-template'
|
||||
|
||||
const props = defineProps<{
|
||||
orgId: string
|
||||
field?: RegistrationFormField | null
|
||||
isSaving: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
save: [payload: Record<string, any>]
|
||||
'update:modelValue': [value: boolean]
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<boolean>({ default: false })
|
||||
|
||||
const orgIdRef = computed(() => props.orgId)
|
||||
const { data: tagCategories } = usePersonTagCategories(orgIdRef)
|
||||
|
||||
const fieldTypeOptions = Object.entries(FIELD_TYPE_LABELS).map(([value, title]) => ({ title, value }))
|
||||
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const refVForm = ref<VForm>()
|
||||
|
||||
const defaultForm = () => ({
|
||||
label: '',
|
||||
field_type: 'text' as string,
|
||||
options: [] as string[],
|
||||
tag_category: null as string | null,
|
||||
is_required: false,
|
||||
is_filterable: false,
|
||||
is_portal_visible: true,
|
||||
is_admin_only: false,
|
||||
section: '',
|
||||
help_text: '',
|
||||
})
|
||||
|
||||
const form = ref(defaultForm())
|
||||
|
||||
const dialogTitle = computed(() =>
|
||||
props.field ? 'Veld bewerken' : 'Nieuw veld',
|
||||
)
|
||||
|
||||
const showOptions = computed(() =>
|
||||
FIELD_TYPES_WITH_OPTIONS.includes(form.value.field_type as any),
|
||||
)
|
||||
|
||||
const showTagCategory = computed(() => form.value.field_type === 'tag_picker')
|
||||
|
||||
watch(modelValue, (open) => {
|
||||
if (open) {
|
||||
errors.value = {}
|
||||
if (props.field) {
|
||||
form.value = {
|
||||
label: props.field.label,
|
||||
field_type: props.field.field_type,
|
||||
options: props.field.options ? [...props.field.options] : [],
|
||||
tag_category: props.field.tag_category,
|
||||
is_required: props.field.is_required,
|
||||
is_filterable: props.field.is_filterable,
|
||||
is_portal_visible: props.field.is_portal_visible,
|
||||
is_admin_only: props.field.is_admin_only,
|
||||
section: props.field.section ?? '',
|
||||
help_text: props.field.help_text ?? '',
|
||||
}
|
||||
}
|
||||
else {
|
||||
form.value = defaultForm()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function addOption() {
|
||||
form.value.options.push('')
|
||||
}
|
||||
|
||||
function removeOption(index: number) {
|
||||
form.value.options.splice(index, 1)
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
refVForm.value?.validate().then(({ valid }) => {
|
||||
if (!valid) return
|
||||
|
||||
errors.value = {}
|
||||
const payload: Record<string, any> = {
|
||||
label: form.value.label,
|
||||
is_required: form.value.is_required,
|
||||
is_filterable: form.value.is_filterable,
|
||||
is_portal_visible: form.value.is_portal_visible,
|
||||
is_admin_only: form.value.is_admin_only,
|
||||
section: form.value.section || null,
|
||||
help_text: form.value.help_text || null,
|
||||
}
|
||||
|
||||
if (!props.field) {
|
||||
payload.field_type = form.value.field_type
|
||||
}
|
||||
|
||||
if (showOptions.value) {
|
||||
payload.options = form.value.options.filter(o => o.trim() !== '')
|
||||
}
|
||||
else {
|
||||
payload.options = null
|
||||
}
|
||||
|
||||
if (showTagCategory.value) {
|
||||
payload.tag_category = form.value.tag_category || null
|
||||
}
|
||||
else {
|
||||
payload.tag_category = null
|
||||
}
|
||||
|
||||
emit('save', payload)
|
||||
})
|
||||
}
|
||||
|
||||
function setErrors(err: any) {
|
||||
const data = err.response?.data
|
||||
if (data?.errors) {
|
||||
errors.value = Object.fromEntries(
|
||||
Object.entries(data.errors).map(([k, v]) => [k, (v as string[])[0]]),
|
||||
)
|
||||
}
|
||||
else if (data?.message) {
|
||||
errors.value = { label: data.message }
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ setErrors })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog
|
||||
v-model="modelValue"
|
||||
max-width="650"
|
||||
>
|
||||
<VCard :title="dialogTitle">
|
||||
<VForm
|
||||
ref="refVForm"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.label"
|
||||
label="Label"
|
||||
placeholder="Hoe heet dit veld?"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.label"
|
||||
autofocus
|
||||
autocomplete="one-time-code"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppSelect
|
||||
v-model="form.field_type"
|
||||
label="Type"
|
||||
:items="fieldTypeOptions"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.field_type"
|
||||
:disabled="!!field"
|
||||
:hint="field ? 'Type kan niet gewijzigd worden na aanmaken' : ''"
|
||||
:persistent-hint="!!field"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- Options (conditional) -->
|
||||
<VCol
|
||||
v-if="showOptions"
|
||||
cols="12"
|
||||
>
|
||||
<label class="text-body-2 mb-2 d-block">Opties</label>
|
||||
<div
|
||||
v-for="(_, index) in form.options"
|
||||
:key="index"
|
||||
class="d-flex align-center gap-x-2 mb-2"
|
||||
>
|
||||
<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)"
|
||||
/>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="tabler-plus"
|
||||
@click="addOption"
|
||||
>
|
||||
Optie toevoegen
|
||||
</VBtn>
|
||||
<p
|
||||
v-if="errors.options"
|
||||
class="text-error text-caption mt-1"
|
||||
>
|
||||
{{ errors.options }}
|
||||
</p>
|
||||
</VCol>
|
||||
|
||||
<!-- Tag category (conditional) -->
|
||||
<VCol
|
||||
v-if="showTagCategory"
|
||||
cols="12"
|
||||
>
|
||||
<AppAutocomplete
|
||||
v-model="form.tag_category"
|
||||
label="Tag-categorie"
|
||||
:items="tagCategories ?? []"
|
||||
clearable
|
||||
:error-messages="errors.tag_category"
|
||||
placeholder="Alle tags (laat leeg voor alle categorieën)"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.section"
|
||||
label="Sectie"
|
||||
placeholder="bijv. Over jou"
|
||||
:error-messages="errors.section"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
/>
|
||||
<VCol cols="12">
|
||||
<AppTextarea
|
||||
v-model="form.help_text"
|
||||
label="Helptekst"
|
||||
placeholder="Toelichting die onder het veld wordt getoond"
|
||||
:error-messages="errors.help_text"
|
||||
rows="2"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- Toggles -->
|
||||
<VCol cols="12">
|
||||
<div class="d-flex flex-wrap gap-x-6 gap-y-2">
|
||||
<VSwitch
|
||||
v-model="form.is_required"
|
||||
label="Verplicht"
|
||||
hide-details
|
||||
density="compact"
|
||||
/>
|
||||
<VSwitch
|
||||
v-model="form.is_filterable"
|
||||
label="Filterbaar bij inplannen"
|
||||
hide-details
|
||||
density="compact"
|
||||
/>
|
||||
<VSwitch
|
||||
v-model="form.is_portal_visible"
|
||||
label="Zichtbaar voor deelnemer"
|
||||
hide-details
|
||||
density="compact"
|
||||
/>
|
||||
<VSwitch
|
||||
v-model="form.is_admin_only"
|
||||
label="Alleen voor organisator"
|
||||
hide-details
|
||||
density="compact"
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="modelValue = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="isSaving"
|
||||
>
|
||||
Opslaan
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VForm>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
141
apps/app/src/components/event/TemplatePickerDialog.vue
Normal file
141
apps/app/src/components/event/TemplatePickerDialog.vue
Normal 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">
|
||||
· {{ template.section }}
|
||||
</template>
|
||||
<template v-if="template.is_required">
|
||||
· 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>
|
||||
Reference in New Issue
Block a user