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:
2026-04-16 18:03:49 +02:00
parent d57dcdb616
commit 6a8d21a5b6
31 changed files with 813 additions and 239 deletions

View File

@@ -263,6 +263,21 @@ function isMultiValueType(t: RegistrationFieldType): boolean {
return t === 'multiselect' || t === 'checkbox' || t === 'tag_picker'
}
function groupedTagItems(tags: Array<{ id: string; name: string; category: string | null }>) {
const items: Array<{ type?: string; title: string; value?: string }> = []
let lastCategory: string | null = null
for (const tag of tags) {
if (tag.category !== lastCategory) {
items.push({ type: 'subheader', title: tag.category ?? 'Overig' })
lastCategory = tag.category
}
items.push({ title: tag.name, value: tag.id })
}
return items
}
function isEmptyFieldValue(value: unknown, type: RegistrationFieldType): boolean {
if (value === undefined || value === null) return true
if (value === '') return true
@@ -1251,7 +1266,7 @@ async function onSubmit() {
:error-messages="fieldErrors[field.slug]"
/>
<VSelect
<VAutocomplete
v-else-if="field.field_type === 'tag_picker'"
v-model="fieldFormData[field.slug] as string[]"
variant="outlined"
@@ -1260,9 +1275,9 @@ async function onSubmit() {
multiple
chips
closable-chips
:items="field.available_tags ?? []"
item-title="name"
item-value="id"
:items="groupedTagItems(field.available_tags ?? [])"
item-title="title"
item-value="value"
:label="field.label + (field.is_required ? ' *' : '')"
:hint="field.help_text ?? undefined"
persistent-hint

View File

@@ -30,7 +30,7 @@ export interface RegistrationField {
field_type: RegistrationFieldType
options: (string | { label: string; description?: string | null })[] | null
normalized_options: NormalizedOption[] | null
tag_category: string | null
tag_categories: string[] | null
is_required: boolean
help_text: string | null
display_width: FieldDisplayWidth