refactor(settings): restructure sidebar and move danger zone to its own tab

Drop the Algemeen tab together with the Organisatie subheader — organisatie-
gegevens verhuizen naar /organisation. Voeg een GEVAARLIJK-subheader toe met
een Gevaarlijke acties tab, die de bestaande platform-beheerder-notitie bevat
(self-delete blijft buiten scope). Legacy ?tab=algemeen/general redirects
door naar /organisation; default tab valt terug op Crowd Types.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 10:27:45 +02:00
parent 671e0c9889
commit 80f0b535f5
4 changed files with 55 additions and 335 deletions

View File

@@ -51,11 +51,11 @@ declare module 'vue' {
CustomRadios: typeof import('./src/@core/components/app-form-elements/CustomRadios.vue')['default']
CustomRadiosWithIcon: typeof import('./src/@core/components/app-form-elements/CustomRadiosWithIcon.vue')['default']
CustomRadiosWithImage: typeof import('./src/@core/components/app-form-elements/CustomRadiosWithImage.vue')['default']
DangerZoneTab: typeof import('./src/components/organisation/settings/DangerZoneTab.vue')['default']
DeleteSubEventDialog: typeof import('./src/components/events/DeleteSubEventDialog.vue')['default']
DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default']
DropZone: typeof import('./src/@core/components/DropZone.vue')['default']
EditEventDialog: typeof import('./src/components/events/EditEventDialog.vue')['default']
EditMemberRoleDialog: typeof import('./src/components/members/EditMemberRoleDialog.vue')['default']
EditOrganisationDialog: typeof import('./src/components/organisations/EditOrganisationDialog.vue')['default']
EditPersonDialog: typeof import('./src/components/persons/EditPersonDialog.vue')['default']
EditSectionDialog: typeof import('./src/components/sections/EditSectionDialog.vue')['default']
@@ -98,8 +98,6 @@ declare module 'vue' {
SettingsEmailBranding: typeof import('./src/components/organisation/settings/SettingsEmailBranding.vue')['default']
SettingsEmailLog: typeof import('./src/components/organisation/settings/SettingsEmailLog.vue')['default']
SettingsEmailTemplates: typeof import('./src/components/organisation/settings/SettingsEmailTemplates.vue')['default']
SettingsGeneral: typeof import('./src/components/organisation/settings/SettingsGeneral.vue')['default']
SettingsMembers: typeof import('./src/components/organisation/settings/SettingsMembers.vue')['default']
SettingsRegistrationFields: typeof import('./src/components/organisation/settings/SettingsRegistrationFields.vue')['default']
SettingsTags: typeof import('./src/components/organisation/settings/SettingsTags.vue')['default']
ShareProjectDialog: typeof import('./src/components/dialogs/ShareProjectDialog.vue')['default']

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
defineProps<{
orgId: string
}>()
</script>
<template>
<VCard
variant="outlined"
color="error"
>
<VCardText>
<h6 class="text-h6 text-error mb-2">
Organisatie verwijderen
</h6>
<p class="text-body-2 mb-4">
Verwijder deze organisatie en alle bijbehorende gegevens permanent. Deze actie kan niet ongedaan worden gemaakt.
</p>
<VAlert
type="info"
variant="tonal"
class="mb-0"
>
Het verwijderen van een organisatie is momenteel niet mogelijk via de applicatie.
Neem contact op met de platform beheerder.
</VAlert>
</VCardText>
</VCard>
</template>

View File

@@ -1,321 +0,0 @@
<script setup lang="ts">
import { VForm } from 'vuetify/components/VForm'
import { useMyOrganisation, useUpdateOrganisation } from '@/composables/api/useOrganisations'
import { useAuthStore } from '@/stores/useAuthStore'
import { requiredValidator } from '@core/utils/validators'
import type { AxiosError } from 'axios'
import type { ApiErrorResponse } from '@/types/auth'
defineProps<{
orgId: string
}>()
const authStore = useAuthStore()
const isOrgAdmin = computed(() => {
const role = authStore.currentOrganisation?.role
return role === 'org_admin' || authStore.isSuperAdmin
})
const { data: organisation, isLoading, isError, refetch } = useMyOrganisation()
const { mutate: updateOrganisation, isPending } = useUpdateOrganisation()
const name = ref('')
const errors = ref<Record<string, string>>({})
const refVForm = ref<VForm>()
const showSuccess = ref(false)
const showCopied = ref(false)
const isDeleteDialogOpen = ref(false)
const showDeleteNotice = ref(false)
watch(() => organisation.value, (org) => {
if (org) {
name.value = org.name
}
}, { immediate: true })
function onSubmit() {
refVForm.value?.validate().then(({ valid }) => {
if (!valid || !organisation.value) return
errors.value = {}
updateOrganisation(
{ id: organisation.value.id, name: name.value },
{
onSuccess: () => {
showSuccess.value = true
},
onError: (err: Error) => {
const data = (err as AxiosError<ApiErrorResponse>).response?.data
if (data?.errors) {
errors.value = { name: data.errors.name?.[0] ?? '' }
}
else if (data?.message) {
errors.value = { name: data.message }
}
},
},
)
})
}
function copySlug() {
if (organisation.value?.slug) {
navigator.clipboard.writeText(organisation.value.slug)
showCopied.value = true
}
}
</script>
<template>
<!-- Loading -->
<VSkeletonLoader
v-if="isLoading"
type="card"
/>
<!-- Error -->
<VAlert
v-else-if="isError"
type="error"
class="mb-4"
>
Kon organisatie niet laden.
<template #append>
<VBtn
variant="text"
@click="refetch()"
>
Opnieuw proberen
</VBtn>
</template>
</VAlert>
<template v-else-if="organisation">
<!-- Organisation details -->
<VCard
title="Organisatiegegevens"
class="mb-6"
>
<VCardText>
<VForm
ref="refVForm"
@submit.prevent="onSubmit"
>
<VRow>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="name"
label="Organisatienaam"
:rules="[requiredValidator]"
:error-messages="errors.name"
:readonly="!isOrgAdmin"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
:model-value="organisation.slug"
label="Slug"
readonly
>
<template #append-inner>
<VBtn
icon="tabler-copy"
variant="text"
size="x-small"
@click="copySlug"
/>
</template>
</AppTextField>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Contactpersoon"
disabled
>
<template #append-inner>
<VTooltip text="Binnenkort beschikbaar">
<template #activator="{ props: tooltipProps }">
<VIcon
v-bind="tooltipProps"
icon="tabler-info-circle"
size="18"
color="disabled"
/>
</template>
</VTooltip>
</template>
</AppTextField>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Contact e-mail"
disabled
>
<template #append-inner>
<VTooltip text="Binnenkort beschikbaar">
<template #activator="{ props: tooltipProps }">
<VIcon
v-bind="tooltipProps"
icon="tabler-info-circle"
size="18"
color="disabled"
/>
</template>
</VTooltip>
</template>
</AppTextField>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Telefoon"
disabled
>
<template #append-inner>
<VTooltip text="Binnenkort beschikbaar">
<template #activator="{ props: tooltipProps }">
<VIcon
v-bind="tooltipProps"
icon="tabler-info-circle"
size="18"
color="disabled"
/>
</template>
</VTooltip>
</template>
</AppTextField>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Website"
disabled
>
<template #append-inner>
<VTooltip text="Binnenkort beschikbaar">
<template #activator="{ props: tooltipProps }">
<VIcon
v-bind="tooltipProps"
icon="tabler-info-circle"
size="18"
color="disabled"
/>
</template>
</VTooltip>
</template>
</AppTextField>
</VCol>
</VRow>
<div
v-if="isOrgAdmin"
class="d-flex justify-end mt-4"
>
<VBtn
type="submit"
:loading="isPending"
>
Wijzigingen opslaan
</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
<!-- Danger zone -->
<VCard
v-if="isOrgAdmin"
class="border-error"
>
<VCardText>
<div class="d-flex align-center justify-space-between">
<div>
<h6 class="text-h6 text-error">
Organisatie verwijderen
</h6>
<p class="text-body-2 text-medium-emphasis mb-0">
Verwijder deze organisatie en alle bijbehorende gegevens permanent. Deze actie kan niet ongedaan worden gemaakt.
</p>
</div>
<VBtn
color="error"
variant="tonal"
class="ms-4"
@click="isDeleteDialogOpen = true"
>
Verwijderen
</VBtn>
</div>
</VCardText>
</VCard>
</template>
<!-- Delete confirmation dialog -->
<VDialog
v-model="isDeleteDialogOpen"
max-width="440"
>
<VCard title="Organisatie verwijderen">
<VCardText class="text-body-1">
Het verwijderen van een organisatie is momenteel niet mogelijk via de applicatie.
Neem contact op met de platform beheerder.
</VCardText>
<VCardActions>
<VSpacer />
<VBtn
variant="text"
@click="isDeleteDialogOpen = false"
>
Sluiten
</VBtn>
<VBtn
color="error"
@click="isDeleteDialogOpen = false; showDeleteNotice = true"
>
Begrepen
</VBtn>
</VCardActions>
</VCard>
</VDialog>
<!-- Snackbars -->
<VSnackbar
v-model="showSuccess"
color="success"
:timeout="3000"
>
Organisatie bijgewerkt
</VSnackbar>
<VSnackbar
v-model="showCopied"
color="info"
:timeout="2000"
>
Slug gekopieerd
</VSnackbar>
<VSnackbar
v-model="showDeleteNotice"
color="info"
:timeout="4000"
>
Neem contact op met de platform beheerder
</VSnackbar>
</template>

View File

@@ -1,12 +1,12 @@
<script setup lang="ts">
import { useOrganisationStore } from '@/stores/useOrganisationStore'
import SettingsGeneral from '@/components/organisation/settings/SettingsGeneral.vue'
import SettingsCrowdTypes from '@/components/organisation/settings/SettingsCrowdTypes.vue'
import SettingsTags from '@/components/organisation/settings/SettingsTags.vue'
import SettingsRegistrationFields from '@/components/organisation/settings/SettingsRegistrationFields.vue'
import SettingsEmailBranding from '@/components/organisation/settings/SettingsEmailBranding.vue'
import SettingsEmailTemplates from '@/components/organisation/settings/SettingsEmailTemplates.vue'
import SettingsEmailLog from '@/components/organisation/settings/SettingsEmailLog.vue'
import DangerZoneTab from '@/components/organisation/settings/DangerZoneTab.vue'
const route = useRoute()
const router = useRouter()
@@ -26,12 +26,6 @@ interface SettingsGroup {
}
const settingsGroups: SettingsGroup[] = [
{
label: 'Organisatie',
items: [
{ icon: 'tabler-building', title: 'Algemeen', key: 'general' },
],
},
{
label: 'Configuratie',
items: [
@@ -53,14 +47,34 @@ const settingsGroups: SettingsGroup[] = [
{ icon: 'tabler-mail-forward', title: 'Verzendlog', key: 'email-log' },
],
},
{
label: 'Gevaarlijk',
items: [
{ icon: 'tabler-alert-triangle', title: 'Gevaarlijke acties', key: 'danger-zone' },
],
},
]
const allKeys = settingsGroups.flatMap(g => g.items.map(i => i.key))
const defaultKey = 'crowd-types'
const legacyGeneralKeys = ['general', 'algemeen', 'gevaarlijke-acties']
onMounted(() => {
const tab = route.query.tab as string | undefined
if (tab === 'general' || tab === 'algemeen') {
router.replace({ path: '/organisation' })
}
else if (tab === 'gevaarlijke-acties') {
router.replace({ query: { tab: 'danger-zone' } })
}
})
const activeKey = computed({
get: () => {
const tab = route.query.tab as string
return allKeys.includes(tab) ? tab : 'general'
const tab = route.query.tab as string | undefined
if (!tab || legacyGeneralKeys.includes(tab))
return defaultKey
return allKeys.includes(tab) ? tab : defaultKey
},
set: (key: string) => {
router.replace({ query: { tab: key } })
@@ -68,16 +82,16 @@ const activeKey = computed({
})
const componentMap: Record<string, Component> = {
'general': SettingsGeneral,
'crowd-types': SettingsCrowdTypes,
'tags': SettingsTags,
'registration-fields': SettingsRegistrationFields,
'email-branding': SettingsEmailBranding,
'email-templates': SettingsEmailTemplates,
'email-log': SettingsEmailLog,
'danger-zone': DangerZoneTab,
}
const activeComponent = computed(() => componentMap[activeKey.value] ?? SettingsGeneral)
const activeComponent = computed(() => componentMap[activeKey.value] ?? SettingsCrowdTypes)
</script>
<template>