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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user