feat: registration field polish, multi-category tags, file uploads, Partner icon
- Restructure field editor dialog: move Options section to bottom with divider and subheader, fix delete button with flex layout - Change tag_category (single string) to tag_categories (JSON array) supporting multiple category selection in tag picker fields - Portal tag picker now groups tags by category with subheaders - Add generic file upload endpoint (FileUploadService + UploadController) - Replace email branding logo URL text field with ImageUploadField - Update Partner crowd type default icon to tabler-affiliate - Apply changes consistently to both field and template dialogs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
131
apps/app/src/components/common/ImageUploadField.vue
Normal file
131
apps/app/src/components/common/ImageUploadField.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { apiClient } from '@/lib/axios'
|
||||
|
||||
interface Props {
|
||||
modelValue: string | null
|
||||
label?: string
|
||||
purpose: 'logo' | 'banner' | 'icon' | 'avatar'
|
||||
hint?: string
|
||||
previewHeight?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
label: undefined,
|
||||
hint: undefined,
|
||||
previewHeight: 120,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [url: string | null]
|
||||
}>()
|
||||
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const uploading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
function triggerFileInput() {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
async function onFileSelected(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
uploading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('purpose', props.purpose)
|
||||
|
||||
const { data } = await apiClient.post<{ data: { url: string } }>('/upload/image', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
|
||||
emit('update:modelValue', data.data.url)
|
||||
}
|
||||
catch (e: unknown) {
|
||||
const axiosError = e as { response?: { data?: { message?: string } } }
|
||||
error.value = axiosError.response?.data?.message ?? 'Upload mislukt'
|
||||
}
|
||||
finally {
|
||||
uploading.value = false
|
||||
if (input) input.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function remove() {
|
||||
emit('update:modelValue', null)
|
||||
error.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
v-if="label"
|
||||
class="text-body-2 text-medium-emphasis d-block mb-1"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
|
||||
<div
|
||||
v-if="modelValue"
|
||||
class="mb-2"
|
||||
>
|
||||
<VImg
|
||||
:src="modelValue"
|
||||
:max-height="previewHeight"
|
||||
max-width="300"
|
||||
class="rounded border"
|
||||
cover
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center gap-2">
|
||||
<VBtn
|
||||
:variant="modelValue ? 'tonal' : 'elevated'"
|
||||
color="primary"
|
||||
size="small"
|
||||
prepend-icon="tabler-upload"
|
||||
:loading="uploading"
|
||||
@click="triggerFileInput"
|
||||
>
|
||||
{{ modelValue ? 'Vervangen' : 'Uploaden' }}
|
||||
</VBtn>
|
||||
<VBtn
|
||||
v-if="modelValue"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
prepend-icon="tabler-trash"
|
||||
@click="remove"
|
||||
>
|
||||
Verwijderen
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none;"
|
||||
@change="onFileSelected"
|
||||
>
|
||||
|
||||
<div
|
||||
v-if="error"
|
||||
class="text-caption text-error mt-1"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="hint"
|
||||
class="text-caption text-medium-emphasis mt-1"
|
||||
>
|
||||
{{ hint }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -107,12 +107,12 @@ function formatOptions(options: NormalizedOption[] | null): string {
|
||||
Opties: {{ formatOptions(field.normalized_options) }}
|
||||
</div>
|
||||
|
||||
<!-- Tag category -->
|
||||
<!-- Tag categories -->
|
||||
<div
|
||||
v-if="field.field_type === 'tag_picker' && field.tag_category"
|
||||
v-if="field.field_type === 'tag_picker' && field.tag_categories?.length"
|
||||
class="text-body-2 text-medium-emphasis mb-1"
|
||||
>
|
||||
Categorie: {{ field.tag_category }}
|
||||
Categorieën: {{ field.tag_categories.join(', ') }}
|
||||
</div>
|
||||
|
||||
<!-- Help text -->
|
||||
|
||||
@@ -52,7 +52,7 @@ const defaultForm = () => ({
|
||||
label: '',
|
||||
field_type: 'text' as string,
|
||||
options: [] as OptionEntry[],
|
||||
tag_category: null as string | null,
|
||||
tag_categories: [] as string[],
|
||||
is_required: false,
|
||||
is_filterable: false,
|
||||
is_portal_visible: true,
|
||||
@@ -85,7 +85,7 @@ watch(modelValue, (open) => {
|
||||
options: props.field.normalized_options
|
||||
? props.field.normalized_options.map(o => ({ label: o.label, description: o.description ?? '' }))
|
||||
: [],
|
||||
tag_category: props.field.tag_category,
|
||||
tag_categories: props.field.tag_categories ?? [],
|
||||
is_required: props.field.is_required,
|
||||
is_filterable: props.field.is_filterable,
|
||||
is_portal_visible: props.field.is_portal_visible,
|
||||
@@ -142,10 +142,10 @@ function onSubmit() {
|
||||
}
|
||||
|
||||
if (showTagCategory.value) {
|
||||
payload.tag_category = form.value.tag_category || null
|
||||
payload.tag_categories = form.value.tag_categories.length > 0 ? form.value.tag_categories : null
|
||||
}
|
||||
else {
|
||||
payload.tag_category = null
|
||||
payload.tag_categories = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,81 +242,13 @@ defineExpose({ setErrors })
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- Options (conditional) -->
|
||||
<VCol
|
||||
v-if="showOptions"
|
||||
cols="12"
|
||||
>
|
||||
<label class="text-body-2 mb-2 d-block">Opties</label>
|
||||
<div
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="mb-3"
|
||||
>
|
||||
<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"
|
||||
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 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>
|
||||
|
||||
@@ -332,15 +264,6 @@ defineExpose({ setErrors })
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<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">
|
||||
@@ -371,6 +294,118 @@ defineExpose({ setErrors })
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<!-- Options section at bottom with visual separation -->
|
||||
<template v-if="showOptions">
|
||||
<VCol cols="12">
|
||||
<VDivider class="mb-1" />
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<div class="d-flex align-center justify-space-between mb-3">
|
||||
<div>
|
||||
<div class="text-subtitle-2">
|
||||
Opties
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
Waaruit kan de deelnemer kiezen?
|
||||
</div>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="tabler-plus"
|
||||
@click="addOption"
|
||||
>
|
||||
Optie toevoegen
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="mb-3"
|
||||
>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-3"
|
||||
>
|
||||
<div class="d-flex align-start gap-2">
|
||||
<VIcon
|
||||
icon="tabler-grip-vertical"
|
||||
class="mt-2 text-medium-emphasis"
|
||||
style="cursor: grab;"
|
||||
/>
|
||||
<div class="flex-grow-1">
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
class="mb-2"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
label="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
placeholder="Korte toelichting die onder de optie verschijnt"
|
||||
counter="200"
|
||||
maxlength="200"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</div>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.options.length === 0"
|
||||
class="text-center text-medium-emphasis py-4"
|
||||
>
|
||||
Nog geen opties. Klik op "Optie toevoegen" om te beginnen.
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="errors.options"
|
||||
class="text-error text-caption mt-1"
|
||||
>
|
||||
{{ errors.options }}
|
||||
</p>
|
||||
</VCol>
|
||||
</template>
|
||||
|
||||
<!-- Tag categories (conditional) -->
|
||||
<template v-if="showTagCategory">
|
||||
<VCol cols="12">
|
||||
<VDivider class="mb-1" />
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<div class="text-subtitle-2 mb-1">
|
||||
Tag-categorieën
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis mb-3">
|
||||
Kies één of meerdere categorieën. De deelnemer ziet alle tags uit deze categorieën.
|
||||
</div>
|
||||
<AppAutocomplete
|
||||
v-model="form.tag_categories"
|
||||
label="Categorieën"
|
||||
:items="tagCategories ?? []"
|
||||
multiple
|
||||
chips
|
||||
closable-chips
|
||||
clearable
|
||||
:error-messages="errors.tag_categories"
|
||||
placeholder="Alle tags (laat leeg voor alle categorieën)"
|
||||
/>
|
||||
</VCol>
|
||||
</template>
|
||||
</template>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { VForm } from 'vuetify/components/VForm'
|
||||
import { useEmailSettings, useUpdateEmailSettings } from '@/composables/api/useEmail'
|
||||
import { emailValidator } from '@core/utils/validators'
|
||||
import ImageUploadField from '@/components/common/ImageUploadField.vue'
|
||||
import type { AxiosError } from 'axios'
|
||||
import type { ApiErrorResponse } from '@/types/auth'
|
||||
|
||||
@@ -116,34 +117,23 @@ function fieldErrors(field: string): string | undefined {
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<VRow>
|
||||
<!-- Logo URL -->
|
||||
<!-- Logo -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<VTextField
|
||||
<ImageUploadField
|
||||
v-model="logoUrl"
|
||||
label="Logo URL"
|
||||
hint="Gebruik een URL naar je logo (PNG of SVG, max 200px hoog)"
|
||||
persistent-hint
|
||||
:error-messages="fieldErrors('logo_url')"
|
||||
label="Logo"
|
||||
purpose="logo"
|
||||
hint="PNG, JPG, SVG of WebP. Max 5MB."
|
||||
:preview-height="80"
|
||||
/>
|
||||
<div
|
||||
v-if="logoUrl"
|
||||
class="mt-2"
|
||||
>
|
||||
<img
|
||||
:src="logoUrl"
|
||||
style="max-height: 48px"
|
||||
@error="($event.target as HTMLImageElement).style.display = 'none'"
|
||||
@load="($event.target as HTMLImageElement).style.display = 'block'"
|
||||
>
|
||||
</div>
|
||||
<p
|
||||
v-else
|
||||
class="text-caption text-disabled mt-2"
|
||||
v-if="fieldErrors('logo_url')"
|
||||
class="text-error text-caption mt-1"
|
||||
>
|
||||
Geen logo ingesteld
|
||||
{{ fieldErrors('logo_url') }}
|
||||
</p>
|
||||
</VCol>
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ const defaultForm = () => ({
|
||||
label: '',
|
||||
field_type: 'text' as string,
|
||||
options: [] as OptionEntry[],
|
||||
tag_category: null as string | null,
|
||||
tag_categories: [] as string[],
|
||||
is_required: false,
|
||||
is_filterable: false,
|
||||
is_portal_visible: true,
|
||||
@@ -113,7 +113,7 @@ function openEditDialog(template: RegistrationFieldTemplate) {
|
||||
options: template.normalized_options
|
||||
? template.normalized_options.map(o => ({ label: o.label, description: o.description ?? '' }))
|
||||
: [],
|
||||
tag_category: template.tag_category,
|
||||
tag_categories: template.tag_categories ?? [],
|
||||
is_required: template.is_required,
|
||||
is_filterable: template.is_filterable,
|
||||
is_portal_visible: template.is_portal_visible,
|
||||
@@ -169,7 +169,7 @@ function onSubmit() {
|
||||
}
|
||||
|
||||
if (showTagCategory.value) {
|
||||
payload.tag_category = form.value.tag_category || null
|
||||
payload.tag_categories = form.value.tag_categories.length > 0 ? form.value.tag_categories : null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,81 +467,13 @@ function activate(template: RegistrationFieldTemplate) {
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- Options (conditional) -->
|
||||
<VCol
|
||||
v-if="showOptions"
|
||||
cols="12"
|
||||
>
|
||||
<label class="text-body-2 mb-2 d-block">Opties</label>
|
||||
<div
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="mb-3"
|
||||
>
|
||||
<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"
|
||||
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 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>
|
||||
|
||||
@@ -571,15 +503,6 @@ function activate(template: RegistrationFieldTemplate) {
|
||||
:error-messages="errors.sort_order"
|
||||
/>
|
||||
</VCol>
|
||||
<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">
|
||||
@@ -610,6 +533,118 @@ function activate(template: RegistrationFieldTemplate) {
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<!-- Options section at bottom with visual separation -->
|
||||
<template v-if="showOptions">
|
||||
<VCol cols="12">
|
||||
<VDivider class="mb-1" />
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<div class="d-flex align-center justify-space-between mb-3">
|
||||
<div>
|
||||
<div class="text-subtitle-2">
|
||||
Opties
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
Waaruit kan de deelnemer kiezen?
|
||||
</div>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
size="small"
|
||||
prepend-icon="tabler-plus"
|
||||
@click="addOption"
|
||||
>
|
||||
Optie toevoegen
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(option, index) in form.options"
|
||||
:key="index"
|
||||
class="mb-3"
|
||||
>
|
||||
<VCard
|
||||
variant="outlined"
|
||||
class="pa-3"
|
||||
>
|
||||
<div class="d-flex align-start gap-2">
|
||||
<VIcon
|
||||
icon="tabler-grip-vertical"
|
||||
class="mt-2 text-medium-emphasis"
|
||||
style="cursor: grab;"
|
||||
/>
|
||||
<div class="flex-grow-1">
|
||||
<AppTextField
|
||||
v-model="option.label"
|
||||
:placeholder="`Optie ${index + 1}`"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
class="mb-2"
|
||||
/>
|
||||
<AppTextField
|
||||
v-model="option.description"
|
||||
label="Beschrijving (optioneel)"
|
||||
density="compact"
|
||||
placeholder="Korte toelichting die onder de optie verschijnt"
|
||||
counter="200"
|
||||
maxlength="200"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</div>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
@click="removeOption(index)"
|
||||
/>
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.options.length === 0"
|
||||
class="text-center text-medium-emphasis py-4"
|
||||
>
|
||||
Nog geen opties. Klik op "Optie toevoegen" om te beginnen.
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="errors.options"
|
||||
class="text-error text-caption mt-1"
|
||||
>
|
||||
{{ errors.options }}
|
||||
</p>
|
||||
</VCol>
|
||||
</template>
|
||||
|
||||
<!-- Tag categories (conditional) -->
|
||||
<template v-if="showTagCategory">
|
||||
<VCol cols="12">
|
||||
<VDivider class="mb-1" />
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<div class="text-subtitle-2 mb-1">
|
||||
Tag-categorieën
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis mb-3">
|
||||
Kies één of meerdere categorieën. De deelnemer ziet alle tags uit deze categorieën.
|
||||
</div>
|
||||
<AppAutocomplete
|
||||
v-model="form.tag_categories"
|
||||
label="Categorieën"
|
||||
:items="tagCategories ?? []"
|
||||
multiple
|
||||
chips
|
||||
closable-chips
|
||||
clearable
|
||||
:error-messages="errors.tag_categories"
|
||||
placeholder="Alle tags (laat leeg voor alle categorieën)"
|
||||
/>
|
||||
</VCol>
|
||||
</template>
|
||||
</template>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface RegistrationFieldTemplate {
|
||||
field_type: RegistrationFieldType
|
||||
options: FieldOption[] | null
|
||||
normalized_options: NormalizedOption[] | null
|
||||
tag_category: string | null
|
||||
tag_categories: string[] | null
|
||||
is_required: boolean
|
||||
is_filterable: boolean
|
||||
is_portal_visible: boolean
|
||||
@@ -65,7 +65,7 @@ export interface RegistrationFieldTemplateCreateDTO {
|
||||
label: string
|
||||
field_type: RegistrationFieldType
|
||||
options?: FieldOption[] | null
|
||||
tag_category?: string | null
|
||||
tag_categories?: string[] | null
|
||||
is_required?: boolean
|
||||
is_filterable?: boolean
|
||||
is_portal_visible?: boolean
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface RegistrationFormField {
|
||||
field_type: RegistrationFieldType
|
||||
options: FieldOption[] | null
|
||||
normalized_options: NormalizedOption[] | null
|
||||
tag_category: string | null
|
||||
tag_categories: string[] | null
|
||||
is_required: boolean
|
||||
is_portal_visible: boolean
|
||||
is_admin_only: boolean
|
||||
@@ -34,7 +34,7 @@ export interface RegistrationFormFieldCreateDTO {
|
||||
label: string
|
||||
field_type: RegistrationFieldType
|
||||
options?: FieldOption[] | null
|
||||
tag_category?: string | null
|
||||
tag_categories?: string[] | null
|
||||
is_required?: boolean
|
||||
is_portal_visible?: boolean
|
||||
is_admin_only?: boolean
|
||||
|
||||
Reference in New Issue
Block a user