refactor: move KPI cards into Algemeen tab, Danger Zone into own tab

Platform org detail:
- KPI cards (events, users, persons) moved inside Algemeen tab
- Danger Zone moved from below tabs into a dedicated "Danger Zone" tab
  with red-colored tab icon
- Tab bar now shows: Algemeen | Leden | Danger Zone

Platform user detail:
- Added VTabs with Algemeen (profile info) and Organisaties tabs
- Timestamps moved below title as muted caption
- Content reorganised into tab structure matching org detail pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 01:04:42 +02:00
parent a8a2bc92d1
commit 9923dab0f8
2 changed files with 203 additions and 188 deletions

View File

@@ -52,6 +52,7 @@ const roleOptions = [
const tabs = [
{ value: 'algemeen', label: 'Algemeen', icon: 'tabler-building' },
{ value: 'leden', label: 'Leden', icon: 'tabler-users-group' },
{ value: 'danger', label: 'Danger Zone', icon: 'tabler-alert-triangle' },
]
const activeTab = computed({
@@ -277,104 +278,17 @@ function formatDate(iso: string): string {
</div>
</div>
<!-- KPI Cards -->
<VRow class="mb-6 mt-4">
<VCol
cols="12"
md="4"
>
<VCard>
<VCardText class="d-flex align-center gap-x-4">
<VAvatar
color="primary"
variant="tonal"
size="44"
rounded
>
<VIcon
icon="tabler-calendar-event"
size="28"
/>
</VAvatar>
<div>
<p class="text-body-1 mb-0">
Events
</p>
<h4 class="text-h4">
{{ org.events_count }}
</h4>
</div>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<VCard>
<VCardText class="d-flex align-center gap-x-4">
<VAvatar
color="success"
variant="tonal"
size="44"
rounded
>
<VIcon
icon="tabler-users-group"
size="28"
/>
</VAvatar>
<div>
<p class="text-body-1 mb-0">
Gebruikers
</p>
<h4 class="text-h4">
{{ org.users_count }}
</h4>
</div>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<VCard>
<VCardText class="d-flex align-center gap-x-4">
<VAvatar
color="info"
variant="tonal"
size="44"
rounded
>
<VIcon
icon="tabler-user"
size="28"
/>
</VAvatar>
<div>
<p class="text-body-1 mb-0">
Totaal personen
</p>
<h4 class="text-h4">
{{ org.total_persons }}
</h4>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- Tabs -->
<VTabs
v-model="activeTab"
class="mb-6"
class="mb-6 mt-4"
>
<VTab
v-for="tab in tabs"
:key="tab.value"
:value="tab.value"
:prepend-icon="tab.icon"
:color="tab.value === 'danger' ? 'error' : undefined"
>
{{ tab.label }}
</VTab>
@@ -386,6 +300,95 @@ function formatDate(iso: string): string {
>
<!-- Algemeen tab -->
<VWindowItem value="algemeen">
<!-- KPI Cards -->
<VRow class="mb-6">
<VCol
cols="12"
md="4"
>
<VCard>
<VCardText class="d-flex align-center gap-x-4">
<VAvatar
color="primary"
variant="tonal"
size="44"
rounded
>
<VIcon
icon="tabler-calendar-event"
size="28"
/>
</VAvatar>
<div>
<p class="text-body-1 mb-0">
Events
</p>
<h4 class="text-h4">
{{ org.events_count }}
</h4>
</div>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<VCard>
<VCardText class="d-flex align-center gap-x-4">
<VAvatar
color="success"
variant="tonal"
size="44"
rounded
>
<VIcon
icon="tabler-users-group"
size="28"
/>
</VAvatar>
<div>
<p class="text-body-1 mb-0">
Gebruikers
</p>
<h4 class="text-h4">
{{ org.users_count }}
</h4>
</div>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<VCard>
<VCardText class="d-flex align-center gap-x-4">
<VAvatar
color="info"
variant="tonal"
size="44"
rounded
>
<VIcon
icon="tabler-user"
size="28"
/>
</VAvatar>
<div>
<p class="text-body-1 mb-0">
Totaal personen
</p>
<h4 class="text-h4">
{{ org.total_persons }}
</h4>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
<!-- Details -->
<VCard>
<VCardTitle>Details</VCardTitle>
<VCardText>
@@ -514,65 +517,65 @@ function formatDate(iso: string): string {
</VCardText>
</VCard>
</VWindowItem>
<!-- Danger Zone tab -->
<VWindowItem value="danger">
<VCard
variant="outlined"
color="error"
>
<VCardText>
<div class="d-flex justify-space-between align-center mb-4">
<div>
<p class="text-subtitle-1 font-weight-medium mb-1">
Verwijder organisatie
</p>
<p class="text-body-2 text-medium-emphasis mb-0">
Alle gegevens van deze organisatie worden permanent verwijderd.
Deze actie kan niet ongedaan worden gemaakt.
</p>
</div>
<VBtn
color="error"
variant="outlined"
class="ms-4 flex-shrink-0"
@click="openDeleteDialog"
>
Verwijder
</VBtn>
</div>
<VDivider class="mb-4" />
<div class="d-flex justify-space-between align-center">
<div>
<p class="text-subtitle-1 font-weight-medium mb-1">
Transfer Ownership
</p>
<p class="text-body-2 text-medium-emphasis mb-0">
Draag het eigenaarschap van deze organisatie over aan een andere gebruiker.
</p>
</div>
<VTooltip location="top">
<template #activator="{ props }">
<span v-bind="props">
<VBtn
color="error"
variant="outlined"
class="ms-4 flex-shrink-0"
disabled
>
Transfer
</VBtn>
</span>
</template>
Nog niet beschikbaar
</VTooltip>
</div>
</VCardText>
</VCard>
</VWindowItem>
</VWindow>
<!-- Danger Zone -->
<VCard
variant="outlined"
color="error"
class="mt-6"
>
<VCardTitle>Danger Zone</VCardTitle>
<VCardText>
<div class="d-flex justify-space-between align-center mb-4">
<div>
<p class="text-subtitle-1 font-weight-medium mb-1">
Verwijder organisatie
</p>
<p class="text-body-2 text-medium-emphasis mb-0">
Alle gegevens van deze organisatie worden permanent verwijderd.
Deze actie kan niet ongedaan worden gemaakt.
</p>
</div>
<VBtn
color="error"
variant="outlined"
class="ms-4 flex-shrink-0"
@click="openDeleteDialog"
>
Verwijder
</VBtn>
</div>
<VDivider class="mb-4" />
<div class="d-flex justify-space-between align-center">
<div>
<p class="text-subtitle-1 font-weight-medium mb-1">
Transfer Ownership
</p>
<p class="text-body-2 text-medium-emphasis mb-0">
Draag het eigenaarschap van deze organisatie over aan een andere gebruiker.
</p>
</div>
<VTooltip location="top">
<template #activator="{ props }">
<span v-bind="props">
<VBtn
color="error"
variant="outlined"
class="ms-4 flex-shrink-0"
disabled
>
Transfer
</VBtn>
</span>
</template>
Nog niet beschikbaar
</VTooltip>
</div>
</VCardText>
</VCard>
</template>
<!-- Edit Dialog -->

View File

@@ -14,10 +14,21 @@ definePage({
})
const route = useRoute()
const router = useRouter()
const impersonationStore = useImpersonationStore()
const userId = computed(() => String((route.params as { id: string }).id))
const activeTab = computed({
get: () => {
const tab = route.query.tab as string
return ['algemeen', 'organisaties'].includes(tab) ? tab : 'algemeen'
},
set: (value: string) => {
router.replace({ query: { ...route.query, tab: value } })
},
})
const { data: user, isLoading, isError, refetch } = useAdminUser(userId)
const roleColorMap: Record<string, string> = {
@@ -123,7 +134,7 @@ function getInitials(name: string): string {
<template v-else-if="user">
<!-- Header -->
<div class="d-flex align-center justify-space-between mb-6">
<div class="d-flex align-center justify-space-between mb-2">
<div class="d-flex align-center gap-x-3">
<VBtn
icon="tabler-arrow-left"
@@ -145,21 +156,23 @@ function getInitials(name: string): string {
<span>{{ getInitials(user.full_name) }}</span>
</VAvatar>
<div>
<h4 class="text-h4">
{{ user.full_name }}
<div class="d-flex align-center gap-x-2">
<h4 class="text-h4">
{{ user.full_name }}
</h4>
<VChip
v-for="role in user.roles"
:key="role"
:color="roleColorMap[role] ?? 'default'"
size="small"
class="ms-1"
>
{{ role }}
</VChip>
</h4>
<p class="text-body-2 text-disabled mb-0">
</div>
<span class="text-caption text-medium-emphasis">
{{ user.email }}
</p>
&middot; Aangemaakt op {{ formatDate(user.created_at) }}
</span>
</div>
</div>
<div class="d-flex gap-x-2">
@@ -181,19 +194,36 @@ function getInitials(name: string): string {
</div>
</div>
<!-- User Info -->
<VRow class="mb-6">
<VCol
cols="12"
md="6"
<!-- Tabs -->
<VTabs
v-model="activeTab"
class="mb-6 mt-4"
>
<VTab
value="algemeen"
prepend-icon="tabler-user"
>
Algemeen
</VTab>
<VTab
value="organisaties"
prepend-icon="tabler-buildings"
>
Organisaties
</VTab>
</VTabs>
<VWindow
v-model="activeTab"
class="disable-tab-transition"
>
<!-- Algemeen tab -->
<VWindowItem value="algemeen">
<VCard>
<VCardTitle>Profiel</VCardTitle>
<VCardText>
<VRow>
<VCol
cols="6"
>
<VCol cols="6">
<p class="text-body-2 text-disabled mb-1">
Tijdzone
</p>
@@ -201,9 +231,7 @@ function getInitials(name: string): string {
{{ user.timezone }}
</p>
</VCol>
<VCol
cols="6"
>
<VCol cols="6">
<p class="text-body-2 text-disabled mb-1">
Taal
</p>
@@ -211,9 +239,7 @@ function getInitials(name: string): string {
{{ user.locale === 'nl' ? 'Nederlands' : 'English' }}
</p>
</VCol>
<VCol
cols="6"
>
<VCol cols="6">
<p class="text-body-2 text-disabled mb-1">
E-mail geverifieerd
</p>
@@ -227,31 +253,17 @@ function getInitials(name: string): string {
{{ user.email_verified_at ? formatDate(user.email_verified_at) : 'Niet geverifieerd' }}
</p>
</VCol>
<VCol
cols="6"
>
<p class="text-body-2 text-disabled mb-1">
Aangemaakt
</p>
<p class="text-body-1">
{{ formatDate(user.created_at) }}
</p>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
</VWindowItem>
<!-- Organisations -->
<VCol
cols="12"
md="6"
>
<!-- Organisaties tab -->
<VWindowItem value="organisaties">
<VCard>
<VCardTitle>Organisaties</VCardTitle>
<VCardText
v-if="user.organisations.length === 0"
class="text-disabled"
class="text-center text-disabled"
>
Geen organisaties
</VCardText>
@@ -276,8 +288,8 @@ function getInitials(name: string): string {
</VListItem>
</VList>
</VCard>
</VCol>
</VRow>
</VWindowItem>
</VWindow>
</template>
<!-- Edit Dialog -->