From 63bc351c59cb00a48f7f8e8dfd1e5c5c7a812408 Mon Sep 17 00:00:00 2001
From: "bert.hausmans"
Date: Sun, 12 Apr 2026 23:18:10 +0200
Subject: [PATCH] refactor(app): unify settings tab design for tags, templates
& crowd types
Move crowd types management to organisation settings as a new tab and
align all three settings tabs (Tags, Registration Field Templates, Crowd
Types) to the same layout pattern: header with title/subtitle, VDataTable
for active items, and a separate inactive section with VList. Also fix
the API to return inactive records for person tags and registration field
templates so the frontend can display them.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
.../Api/V1/PersonTagController.php | 2 +-
.../RegistrationFieldTemplateService.php | 1 -
api/tests/Feature/PersonTag/PersonTagTest.php | 2 +-
.../RegistrationFieldTemplateTest.php | 4 +-
.../RegistrationFieldTemplatesTab.vue | 103 +++--
.../organisations/CrowdTypesManager.vue | 377 +++++++++---------
apps/app/src/pages/organisation/index.vue | 8 -
apps/app/src/pages/organisation/settings.vue | 5 +
8 files changed, 268 insertions(+), 234 deletions(-)
diff --git a/api/app/Http/Controllers/Api/V1/PersonTagController.php b/api/app/Http/Controllers/Api/V1/PersonTagController.php
index 54d6d9fc..74a34216 100644
--- a/api/app/Http/Controllers/Api/V1/PersonTagController.php
+++ b/api/app/Http/Controllers/Api/V1/PersonTagController.php
@@ -20,7 +20,7 @@ final class PersonTagController extends Controller
{
Gate::authorize('viewAny', [PersonTag::class, $organisation]);
- $tags = $organisation->personTags()->active()->ordered()->get();
+ $tags = $organisation->personTags()->ordered()->get();
return PersonTagResource::collection($tags);
}
diff --git a/api/app/Services/RegistrationFieldTemplateService.php b/api/app/Services/RegistrationFieldTemplateService.php
index d6c16b54..48bc8022 100644
--- a/api/app/Services/RegistrationFieldTemplateService.php
+++ b/api/app/Services/RegistrationFieldTemplateService.php
@@ -16,7 +16,6 @@ final class RegistrationFieldTemplateService
public function listForOrganisation(Organisation $organisation): Collection
{
return $organisation->registrationFieldTemplates()
- ->active()
->ordered()
->get();
}
diff --git a/api/tests/Feature/PersonTag/PersonTagTest.php b/api/tests/Feature/PersonTag/PersonTagTest.php
index e0e7ee45..e302ba9a 100644
--- a/api/tests/Feature/PersonTag/PersonTagTest.php
+++ b/api/tests/Feature/PersonTag/PersonTagTest.php
@@ -51,7 +51,7 @@ class PersonTagTest extends TestCase
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/person-tags");
$response->assertOk();
- $this->assertCount(3, $response->json('data'));
+ $this->assertCount(4, $response->json('data'));
}
public function test_store_creates_tag(): void
diff --git a/api/tests/Feature/RegistrationFieldTemplate/RegistrationFieldTemplateTest.php b/api/tests/Feature/RegistrationFieldTemplate/RegistrationFieldTemplateTest.php
index b0c6bed2..66128d60 100644
--- a/api/tests/Feature/RegistrationFieldTemplate/RegistrationFieldTemplateTest.php
+++ b/api/tests/Feature/RegistrationFieldTemplate/RegistrationFieldTemplateTest.php
@@ -39,7 +39,7 @@ class RegistrationFieldTemplateTest extends TestCase
$this->otherOrganisation->users()->attach($this->outsider, ['role' => 'org_admin']);
}
- public function test_index_returns_active_templates(): void
+ public function test_index_returns_all_templates(): void
{
RegistrationFieldTemplate::factory()->count(3)->create([
'organisation_id' => $this->organisation->id,
@@ -53,7 +53,7 @@ class RegistrationFieldTemplateTest extends TestCase
$response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/registration-field-templates");
$response->assertOk();
- $this->assertCount(3, $response->json('data'));
+ $this->assertCount(4, $response->json('data'));
}
public function test_store_creates_template(): void
diff --git a/apps/app/src/components/organisation/RegistrationFieldTemplatesTab.vue b/apps/app/src/components/organisation/RegistrationFieldTemplatesTab.vue
index d991da18..f1e4efc1 100644
--- a/apps/app/src/components/organisation/RegistrationFieldTemplatesTab.vue
+++ b/apps/app/src/components/organisation/RegistrationFieldTemplatesTab.vue
@@ -25,12 +25,14 @@ const { mutate: deleteTemplate, isPending: isDeleting } = useDeleteRegistrationF
const fieldTypeOptions = Object.entries(FIELD_TYPE_LABELS).map(([value, title]) => ({ title, value }))
+const activeTemplates = computed(() => templates.value?.filter(t => t.is_active) ?? [])
+const inactiveTemplates = computed(() => templates.value?.filter(t => !t.is_active) ?? [])
+
const headers = [
{ title: 'Label', key: 'label' },
{ title: 'Type', key: 'field_type' },
{ title: 'Sectie', key: 'section' },
{ title: 'Systeem', key: 'is_system', sortable: false },
- { title: 'Status', key: 'is_active', sortable: false },
{ title: 'Acties', key: 'actions', sortable: false, align: 'end' as const },
]
@@ -199,29 +201,25 @@ function onDeleteExecute() {
})
}
-function toggleActive(template: RegistrationFieldTemplate) {
- if (template.is_system) {
- // System templates: toggle via DELETE (deactivate) or UPDATE (activate)
- if (template.is_active) {
- deleteTemplate(template.id, {
- onSuccess: () => {
- successMessage.value = `${template.label} gedeactiveerd`
- showSuccess.value = true
- },
- })
- }
- else {
- updateTemplate(
- { id: template.id, is_active: true },
- {
- onSuccess: () => {
- successMessage.value = `${template.label} geactiveerd`
- showSuccess.value = true
- },
- },
- )
- }
- }
+function deactivate(template: RegistrationFieldTemplate) {
+ deleteTemplate(template.id, {
+ onSuccess: () => {
+ successMessage.value = `${template.label} gedeactiveerd`
+ showSuccess.value = true
+ },
+ })
+}
+
+function activate(template: RegistrationFieldTemplate) {
+ updateTemplate(
+ { id: template.id, is_active: true },
+ {
+ onSuccess: () => {
+ successMessage.value = `${template.label} geactiveerd`
+ showSuccess.value = true
+ },
+ },
+ )
}
@@ -267,11 +265,11 @@ function toggleActive(template: RegistrationFieldTemplate) {
-
-
+
+
-
-
- {{ item.is_active ? 'Actief' : 'Inactief' }}
-
-
-
+
+
+
+
+ Inactief
+
+
+
+
+
+ {{ template.label }}
+
+
+ {{ FIELD_TYPE_LABELS[template.field_type] ?? template.field_type }}
+
+
+
+
+ Activeren
+
+
+
+
+
+
diff --git a/apps/app/src/components/organisations/CrowdTypesManager.vue b/apps/app/src/components/organisations/CrowdTypesManager.vue
index 44c762b6..1562fca7 100644
--- a/apps/app/src/components/organisations/CrowdTypesManager.vue
+++ b/apps/app/src/components/organisations/CrowdTypesManager.vue
@@ -57,6 +57,14 @@ const inactiveCrowdTypes = computed(() =>
crowdTypes.value?.filter(ct => !ct.is_active) ?? [],
)
+const headers = [
+ { title: 'Naam', key: 'name' },
+ { title: 'Systeemtype', key: 'system_type' },
+ { title: 'Icoon', key: 'icon', sortable: false },
+ { title: 'Kleur', key: 'color', sortable: false },
+ { title: 'Acties', key: 'actions', sortable: false, align: 'end' as const },
+]
+
const dialogTitle = computed(() =>
editingCrowdType.value ? 'Crowd type bewerken' : 'Crowd type aanmaken',
)
@@ -155,130 +163,139 @@ function activate(ct: CrowdType) {
-
-
- Crowd types
- Definieer de deelnemertypes voor je organisatie
-
+
+
+
+
+
+
+
+
+
+ Crowd types
+
+
+ Definieer de deelnemertypes voor je organisatie
+
+
Crowd type toevoegen
-
-
+
-
-
-
-
-
-
-
+
+
+
+
Nog geen crowd types aangemaakt. Maak er een aan om personen te categoriseren.
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+ {{ item.name }}
+
- {{ ct.name }}
-
-
- {{ systemTypeLabels[ct.system_type] ?? ct.system_type }}
-
-
+
+
+ {{ systemTypeLabels[item.system_type] ?? item.system_type }}
+
+
-
+
+
+ -
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
-
-
-
-
- Inactief
-
-
+
+
+
+ Inactief
+
+
+
-
-
-
+
{{ ct.name }}
-
- {{ systemTypeLabels[ct.system_type] ?? ct.system_type }}
-
+ {{ systemTypeLabels[ct.system_type] ?? ct.system_type }}
@@ -293,101 +310,101 @@ function activate(ct: CrowdType) {
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
- Kleur
+
+
+ {{ errors.color }}
+
+
+
- {{ errors.color }}
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
- Annuleren
-
-
- Opslaan
-
-
-
-
-
+ Annuleren
+
+
+ Opslaan
+
+
+
+
+
-
- {{ successMessage }}
-
+
+ {{ successMessage }}
+
+
diff --git a/apps/app/src/pages/organisation/index.vue b/apps/app/src/pages/organisation/index.vue
index 0beb081b..844c726b 100644
--- a/apps/app/src/pages/organisation/index.vue
+++ b/apps/app/src/pages/organisation/index.vue
@@ -2,7 +2,6 @@
import { useMyOrganisation } from '@/composables/api/useOrganisations'
import { useAuthStore } from '@/stores/useAuthStore'
import EditOrganisationDialog from '@/components/organisations/EditOrganisationDialog.vue'
-import CrowdTypesManager from '@/components/organisations/CrowdTypesManager.vue'
import type { Organisation } from '@/types/organisation'
const authStore = useAuthStore()
@@ -124,13 +123,6 @@ function formatDate(iso: string) {
-
-
-
orgStore.activeOrganisationId ?? '')
const tabs = [
{ value: 'tags', label: 'Tags & Vaardigheden', icon: 'tabler-tag' },
{ value: 'templates', label: 'Registratieveld-templates', icon: 'tabler-forms' },
+ { value: 'crowd-types', label: 'Crowd types', icon: 'tabler-users-group' },
]
const activeTab = computed({
@@ -62,6 +64,9 @@ const activeTab = computed({
+
+
+