refactor: identical VDataTable for members on both organisation pages

Both org pages now use the same VDataTable with:
- Search field (name/email filter)
- Sortable columns (Naam, E-mail, Rol) with default sort on name
- Pagination (10 per page)
- Avatar with initials, role chips with color mapping
- Consistent empty state with icon

Platform page: replaced VTable with VDataTable, added role chips
(replacing inline AppSelect), role editing via menu on edit button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 01:22:01 +02:00
parent ca275723db
commit 66e4167c03
2 changed files with 215 additions and 112 deletions

View File

@@ -95,18 +95,22 @@ const roleLabelMap: Record<string, string> = {
volunteer_coordinator: 'Vrijwilliger Coördinator',
}
const memberSearch = ref('')
const memberHeaders = computed(() => {
const headers: Array<{ title: string; key: string; sortable?: boolean }> = [
{ title: 'Naam', key: 'full_name' },
{ title: 'E-mailadres', key: 'email' },
{ title: 'Rol', key: 'role' },
const headers: Array<{ title: string; key: string; sortable?: boolean; align?: 'start' | 'end' }> = [
{ title: 'Naam', key: 'full_name', sortable: true },
{ title: 'E-mailadres', key: 'email', sortable: true },
{ title: 'Rol', key: 'role', sortable: true },
]
if (isOrgAdmin.value) {
headers.push({ title: 'Acties', key: 'actions', sortable: false })
headers.push({ title: 'Acties', key: 'actions', sortable: false, align: 'end' })
}
return headers
})
const memberSortBy = [{ key: 'full_name', order: 'asc' as const }]
function getInitials(name: string): string {
return name
.split(' ')
@@ -328,32 +332,43 @@ function confirmRevokeInvitation() {
</VAlert>
<template v-else>
<!-- Invite button -->
<div
v-if="isOrgAdmin"
class="d-flex justify-end mb-4"
>
<VBtn
prepend-icon="tabler-user-plus"
@click="isInviteDialogOpen = true"
>
Lid uitnodigen
</VBtn>
</div>
<!-- Members table -->
<VCard class="mb-6">
<VCardText v-if="members.length === 0">
<p class="text-body-1 text-disabled mb-0">
Nog geen leden
</p>
<!-- Search + Invite -->
<VCardText>
<VRow>
<VCol
cols="12"
md="6"
>
<AppTextField
v-model="memberSearch"
placeholder="Zoek op naam of e-mail..."
prepend-inner-icon="tabler-search"
clearable
/>
</VCol>
<VCol
v-if="isOrgAdmin"
cols="12"
md="6"
class="d-flex justify-end"
>
<VBtn
prepend-icon="tabler-user-plus"
@click="isInviteDialogOpen = true"
>
Lid uitnodigen
</VBtn>
</VCol>
</VRow>
</VCardText>
<VDataTable
v-else
:headers="memberHeaders"
:items="members"
:items-per-page="-1"
hide-default-footer
:search="memberSearch"
:sort-by="memberSortBy"
:items-per-page="10"
>
<template #item.full_name="{ item }">
<div class="d-flex align-center gap-x-3">
@@ -384,7 +399,7 @@ function confirmRevokeInvitation() {
</template>
<template #item.actions="{ item }">
<div class="d-flex gap-x-1">
<div class="d-flex gap-x-1 justify-end">
<VBtn
icon="tabler-edit"
variant="text"
@@ -406,6 +421,17 @@ function confirmRevokeInvitation() {
/>
</div>
</template>
<template #no-data>
<div class="text-center pa-4 text-disabled">
<VIcon
icon="tabler-users-minus"
size="48"
class="mb-2"
/>
<p>Geen leden gevonden</p>
</div>
</template>
</VDataTable>
</VCard>