feat(organisation): expand /organisation page to full dashboard
Replace the minimal placeholder with a dashboard: header + edit action, drie stat-tegels (Leden / Evenementen / Personen — de eerste twee clickable), organisatiegegevens + leden-top-5 infokaarten en een recente- activiteit lijst. Nieuwe TypeScript-types en useOrganisationDashboardStats composable sluiten aan op de nieuwe backend-endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { apiClient } from '@/lib/axios'
|
|||||||
import { useAuthStore } from '@/stores/useAuthStore'
|
import { useAuthStore } from '@/stores/useAuthStore'
|
||||||
import type {
|
import type {
|
||||||
Organisation,
|
Organisation,
|
||||||
|
OrganisationDashboardStats,
|
||||||
UpdateOrganisationPayload,
|
UpdateOrganisationPayload,
|
||||||
} from '@/types/organisation'
|
} from '@/types/organisation'
|
||||||
|
|
||||||
@@ -70,6 +71,18 @@ export function useUpdateOrganisation() {
|
|||||||
onSuccess: (_data, variables) => {
|
onSuccess: (_data, variables) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['organisations'] })
|
queryClient.invalidateQueries({ queryKey: ['organisations'] })
|
||||||
queryClient.invalidateQueries({ queryKey: ['organisations', variables.id] })
|
queryClient.invalidateQueries({ queryKey: ['organisations', variables.id] })
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['organisation-dashboard-stats', variables.id] })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useOrganisationDashboardStats(id: Ref<string>) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['organisation-dashboard-stats', id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiClient.get<ApiResponse<OrganisationDashboardStats>>(`/organisations/${id.value}/dashboard-stats`)
|
||||||
|
return data.data
|
||||||
|
},
|
||||||
|
enabled: () => !!id.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMyOrganisation } from '@/composables/api/useOrganisations'
|
import { useMyOrganisation, useOrganisationDashboardStats } from '@/composables/api/useOrganisations'
|
||||||
import { useAuthStore } from '@/stores/useAuthStore'
|
import { useAuthStore } from '@/stores/useAuthStore'
|
||||||
import EditOrganisationDialog from '@/components/organisations/EditOrganisationDialog.vue'
|
import EditOrganisationDialog from '@/components/organisations/EditOrganisationDialog.vue'
|
||||||
import type { Organisation } from '@/types/organisation'
|
import type { ActivityLogEntry, Organisation } from '@/types/organisation'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const { data: organisation, isLoading, isError, refetch } = useMyOrganisation()
|
const { data: organisation, isLoading: orgLoading, isError: orgError, refetch: refetchOrg } = useMyOrganisation()
|
||||||
|
|
||||||
|
const orgId = computed(() => organisation.value?.id ?? '')
|
||||||
|
const {
|
||||||
|
data: stats,
|
||||||
|
isLoading: statsLoading,
|
||||||
|
isError: statsError,
|
||||||
|
refetch: refetchStats,
|
||||||
|
} = useOrganisationDashboardStats(orgId)
|
||||||
|
|
||||||
const isOrgAdmin = computed(() => {
|
const isOrgAdmin = computed(() => {
|
||||||
const role = authStore.currentOrganisation?.role
|
const role = authStore.currentOrganisation?.role
|
||||||
@@ -22,11 +31,68 @@ const statusColor: Record<Organisation['billing_status'], string> = {
|
|||||||
|
|
||||||
const isEditDialogOpen = ref(false)
|
const isEditDialogOpen = ref(false)
|
||||||
|
|
||||||
function formatDate(iso: string) {
|
const activeSubtitle = computed(() => {
|
||||||
const d = new Date(iso)
|
const active = stats.value?.active_events_count ?? 0
|
||||||
const date = d.toLocaleDateString('nl-NL', { day: 'numeric', month: 'long', year: 'numeric' })
|
if (active > 0) return `${active} actief`
|
||||||
const time = d.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' })
|
return 'Nog geen actieve evenementen'
|
||||||
return `${date} om ${time} uur`
|
})
|
||||||
|
|
||||||
|
const roleColor: Record<string, string> = {
|
||||||
|
org_admin: 'primary',
|
||||||
|
org_member: 'secondary',
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleLabel: Record<string, string> = {
|
||||||
|
org_admin: 'Admin',
|
||||||
|
org_member: 'Lid',
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToMembers() {
|
||||||
|
router.push('/members')
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToEvents() {
|
||||||
|
router.push('/events')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRelativeTime(iso: string | null): string {
|
||||||
|
if (!iso) return ''
|
||||||
|
const date = new Date(iso)
|
||||||
|
const diffMs = Date.now() - date.getTime()
|
||||||
|
const diffSec = Math.round(diffMs / 1000)
|
||||||
|
if (diffSec < 60) return 'zojuist'
|
||||||
|
const diffMin = Math.round(diffSec / 60)
|
||||||
|
if (diffMin < 60) return `${diffMin} min geleden`
|
||||||
|
const diffHr = Math.round(diffMin / 60)
|
||||||
|
if (diffHr < 24) return `${diffHr} uur geleden`
|
||||||
|
const diffDays = Math.round(diffHr / 24)
|
||||||
|
if (diffDays < 7) return `${diffDays} dag${diffDays === 1 ? '' : 'en'} geleden`
|
||||||
|
return date.toLocaleDateString('nl-NL', { day: 'numeric', month: 'short', year: 'numeric' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function avatarInitials(name: string | null | undefined): string {
|
||||||
|
if (!name) return '?'
|
||||||
|
return name
|
||||||
|
.split(' ')
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 2)
|
||||||
|
.map(part => part[0]?.toUpperCase() ?? '')
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeActivity(entry: ActivityLogEntry): string {
|
||||||
|
const changed = Object.keys(entry.properties?.attributes ?? {})
|
||||||
|
const labelMap: Record<string, string> = {
|
||||||
|
name: 'naam',
|
||||||
|
slug: 'slug',
|
||||||
|
contact_name: 'contactpersoon',
|
||||||
|
contact_email: 'contact e-mail',
|
||||||
|
phone: 'telefoonnummer',
|
||||||
|
website: 'website',
|
||||||
|
}
|
||||||
|
if (changed.length === 0) return entry.description
|
||||||
|
const fields = changed.map(f => labelMap[f] ?? f).join(', ')
|
||||||
|
return `Wijzigde ${fields}`
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -34,13 +100,13 @@ function formatDate(iso: string) {
|
|||||||
<div>
|
<div>
|
||||||
<!-- Loading -->
|
<!-- Loading -->
|
||||||
<VSkeletonLoader
|
<VSkeletonLoader
|
||||||
v-if="isLoading"
|
v-if="orgLoading"
|
||||||
type="card"
|
type="article, article"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Error -->
|
<!-- Error -->
|
||||||
<VAlert
|
<VAlert
|
||||||
v-else-if="isError"
|
v-else-if="orgError"
|
||||||
type="error"
|
type="error"
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
>
|
>
|
||||||
@@ -48,7 +114,7 @@ function formatDate(iso: string) {
|
|||||||
<template #append>
|
<template #append>
|
||||||
<VBtn
|
<VBtn
|
||||||
variant="text"
|
variant="text"
|
||||||
@click="refetch()"
|
@click="refetchOrg()"
|
||||||
>
|
>
|
||||||
Opnieuw proberen
|
Opnieuw proberen
|
||||||
</VBtn>
|
</VBtn>
|
||||||
@@ -57,12 +123,11 @@ function formatDate(iso: string) {
|
|||||||
|
|
||||||
<template v-else-if="organisation">
|
<template v-else-if="organisation">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="d-flex align-center justify-space-between mb-2">
|
<div class="d-flex align-center justify-space-between flex-wrap gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<div class="d-flex align-center gap-x-2">
|
<div class="d-flex align-center gap-x-2 flex-wrap">
|
||||||
<h4 class="text-h4">
|
<h4 class="text-h4 mb-0">
|
||||||
{{ organisation.name }}
|
{{ organisation.name }}
|
||||||
<span class="text-body-2 text-medium-emphasis font-weight-regular ms-2">({{ organisation.slug }})</span>
|
|
||||||
</h4>
|
</h4>
|
||||||
<VChip
|
<VChip
|
||||||
:color="statusColor[organisation.billing_status]"
|
:color="statusColor[organisation.billing_status]"
|
||||||
@@ -71,51 +136,423 @@ function formatDate(iso: string) {
|
|||||||
{{ organisation.billing_status.charAt(0).toUpperCase() + organisation.billing_status.slice(1) }}
|
{{ organisation.billing_status.charAt(0).toUpperCase() + organisation.billing_status.slice(1) }}
|
||||||
</VChip>
|
</VChip>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-body-1 text-disabled mb-0">
|
<code class="text-caption text-primary d-block mt-1">{{ organisation.slug }}</code>
|
||||||
Aangemaakt op {{ formatDate(organisation.created_at) }}
|
|
||||||
· Gewijzigd op {{ formatDate(organisation.updated_at) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<VBtn
|
<VBtn
|
||||||
v-if="isOrgAdmin"
|
v-if="isOrgAdmin"
|
||||||
prepend-icon="tabler-edit"
|
prepend-icon="tabler-edit"
|
||||||
@click="isEditDialogOpen = true"
|
@click="isEditDialogOpen = true"
|
||||||
>
|
>
|
||||||
Bewerken
|
Organisatie bewerken
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VCard>
|
<!-- Stat cards -->
|
||||||
<VCardText>
|
<VRow
|
||||||
<VRow>
|
v-if="statsLoading"
|
||||||
<VCol
|
class="mb-6"
|
||||||
cols="12"
|
>
|
||||||
md="4"
|
<VCol
|
||||||
>
|
v-for="n in 3"
|
||||||
<h6 class="text-h6 mb-1">
|
:key="n"
|
||||||
Slug
|
cols="12"
|
||||||
</h6>
|
md="4"
|
||||||
<p class="text-body-1 text-disabled mb-0">
|
>
|
||||||
{{ organisation.slug }}
|
<VCard>
|
||||||
|
<VCardText>
|
||||||
|
<VSkeletonLoader type="heading" />
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
|
||||||
|
<VAlert
|
||||||
|
v-else-if="statsError"
|
||||||
|
type="error"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
Kon statistieken niet laden.
|
||||||
|
<template #append>
|
||||||
|
<VBtn
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
@click="refetchStats()"
|
||||||
|
>
|
||||||
|
Opnieuw proberen
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
</VAlert>
|
||||||
|
|
||||||
|
<VRow
|
||||||
|
v-else-if="stats"
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<VCard
|
||||||
|
class="cursor-pointer h-100 card-border-shadow-primary"
|
||||||
|
@click="goToMembers"
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<div class="d-flex align-center mb-1">
|
||||||
|
<VAvatar
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="44"
|
||||||
|
rounded
|
||||||
|
class="me-4"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-users"
|
||||||
|
size="28"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
<h4 class="text-h4 mb-0">
|
||||||
|
{{ stats.members_count }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">
|
||||||
|
Leden
|
||||||
</p>
|
</p>
|
||||||
</VCol>
|
<p class="mb-0 text-body-secondary text-sm">
|
||||||
<VCol
|
Actieve leden in je organisatie
|
||||||
cols="12"
|
</p>
|
||||||
md="4"
|
</VCardText>
|
||||||
>
|
</VCard>
|
||||||
<h6 class="text-h6 mb-1">
|
</VCol>
|
||||||
Status
|
|
||||||
</h6>
|
<VCol
|
||||||
<VChip
|
cols="12"
|
||||||
:color="statusColor[organisation.billing_status]"
|
md="4"
|
||||||
size="small"
|
>
|
||||||
|
<VCard
|
||||||
|
class="cursor-pointer h-100 card-border-shadow-info"
|
||||||
|
@click="goToEvents"
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<div class="d-flex align-center mb-1">
|
||||||
|
<VAvatar
|
||||||
|
color="info"
|
||||||
|
variant="tonal"
|
||||||
|
size="44"
|
||||||
|
rounded
|
||||||
|
class="me-4"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-calendar-event"
|
||||||
|
size="28"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
<h4 class="text-h4 mb-0">
|
||||||
|
{{ stats.events_count }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">
|
||||||
|
Evenementen
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 text-body-secondary text-sm">
|
||||||
|
{{ activeSubtitle }}
|
||||||
|
</p>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<VCard class="h-100 card-border-shadow-success">
|
||||||
|
<VCardText>
|
||||||
|
<div class="d-flex align-center mb-1">
|
||||||
|
<VAvatar
|
||||||
|
color="success"
|
||||||
|
variant="tonal"
|
||||||
|
size="44"
|
||||||
|
rounded
|
||||||
|
class="me-4"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-user-circle"
|
||||||
|
size="28"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
<h4 class="text-h4 mb-0">
|
||||||
|
{{ stats.persons_count }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">
|
||||||
|
Personen
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 text-body-secondary text-sm">
|
||||||
|
Over alle evenementen heen
|
||||||
|
</p>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
|
||||||
|
<!-- Info cards row -->
|
||||||
|
<VRow>
|
||||||
|
<!-- Organisation details -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<VCard>
|
||||||
|
<VCardItem>
|
||||||
|
<template #title>
|
||||||
|
Organisatiegegevens
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-if="isOrgAdmin"
|
||||||
|
#append
|
||||||
>
|
>
|
||||||
{{ organisation.billing_status }}
|
<VBtn
|
||||||
</VChip>
|
variant="text"
|
||||||
</VCol>
|
size="small"
|
||||||
</VRow>
|
icon="tabler-edit"
|
||||||
</VCardText>
|
@click="isEditDialogOpen = true"
|
||||||
</VCard>
|
/>
|
||||||
|
</template>
|
||||||
|
</VCardItem>
|
||||||
|
<VDivider />
|
||||||
|
<VList
|
||||||
|
lines="two"
|
||||||
|
density="comfortable"
|
||||||
|
>
|
||||||
|
<VListItem>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-medium-emphasis text-caption">Naam</span>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<span class="text-body-1">{{ organisation.name }}</span>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-medium-emphasis text-caption">Slug</span>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<code class="text-body-2 text-primary">{{ organisation.slug }}</code>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-medium-emphasis text-caption">Contactpersoon</span>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<span class="text-body-1">{{ organisation.contact_name || '—' }}</span>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-medium-emphasis text-caption">Contact e-mail</span>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<a
|
||||||
|
v-if="organisation.contact_email"
|
||||||
|
:href="`mailto:${organisation.contact_email}`"
|
||||||
|
class="text-body-1 text-primary"
|
||||||
|
>{{ organisation.contact_email }}</a>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="text-body-1"
|
||||||
|
>—</span>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-medium-emphasis text-caption">Telefoon</span>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<span class="text-body-1">{{ organisation.phone || '—' }}</span>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-medium-emphasis text-caption">Website</span>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<a
|
||||||
|
v-if="organisation.website"
|
||||||
|
:href="organisation.website"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="text-body-1 text-primary"
|
||||||
|
>{{ organisation.website }}</a>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="text-body-1"
|
||||||
|
>—</span>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- Members card -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<VCard class="h-100">
|
||||||
|
<VCardItem>
|
||||||
|
<template #title>
|
||||||
|
<span>Leden</span>
|
||||||
|
<span
|
||||||
|
v-if="stats"
|
||||||
|
class="text-medium-emphasis text-body-2 ms-2"
|
||||||
|
>({{ stats.members_count }})</span>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<VBtn
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
append-icon="tabler-arrow-right"
|
||||||
|
@click="goToMembers"
|
||||||
|
>
|
||||||
|
Alle leden
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
</VCardItem>
|
||||||
|
<VDivider />
|
||||||
|
|
||||||
|
<VSkeletonLoader
|
||||||
|
v-if="statsLoading"
|
||||||
|
type="list-item-avatar@5"
|
||||||
|
/>
|
||||||
|
<VAlert
|
||||||
|
v-else-if="statsError"
|
||||||
|
type="error"
|
||||||
|
variant="tonal"
|
||||||
|
class="ma-4"
|
||||||
|
>
|
||||||
|
Kon leden niet laden.
|
||||||
|
</VAlert>
|
||||||
|
<div
|
||||||
|
v-else-if="stats && stats.top_members.length === 0"
|
||||||
|
class="pa-6 text-center text-medium-emphasis"
|
||||||
|
>
|
||||||
|
Nog geen leden
|
||||||
|
</div>
|
||||||
|
<VList
|
||||||
|
v-else-if="stats"
|
||||||
|
lines="two"
|
||||||
|
density="comfortable"
|
||||||
|
>
|
||||||
|
<VListItem
|
||||||
|
v-for="member in stats.top_members"
|
||||||
|
:key="member.id"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VAvatar
|
||||||
|
:image="member.avatar_url ?? undefined"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="36"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="!member.avatar_url"
|
||||||
|
class="text-sm"
|
||||||
|
>{{ avatarInitials(member.name) }}</span>
|
||||||
|
</VAvatar>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
{{ member.name }}
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<span class="text-body-2 text-medium-emphasis">{{ member.email }}</span>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<VChip
|
||||||
|
:color="roleColor[member.role] ?? 'secondary'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ roleLabel[member.role] ?? member.role }}
|
||||||
|
</VChip>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
|
||||||
|
<!-- Activity log -->
|
||||||
|
<VRow class="mt-2">
|
||||||
|
<VCol cols="12">
|
||||||
|
<VCard>
|
||||||
|
<VCardItem>
|
||||||
|
<template #title>
|
||||||
|
Recente activiteit
|
||||||
|
</template>
|
||||||
|
</VCardItem>
|
||||||
|
<VDivider />
|
||||||
|
|
||||||
|
<VSkeletonLoader
|
||||||
|
v-if="statsLoading"
|
||||||
|
type="list-item-avatar@5"
|
||||||
|
/>
|
||||||
|
<VAlert
|
||||||
|
v-else-if="statsError"
|
||||||
|
type="error"
|
||||||
|
variant="tonal"
|
||||||
|
class="ma-4"
|
||||||
|
>
|
||||||
|
Kon activiteiten niet laden.
|
||||||
|
<template #append>
|
||||||
|
<VBtn
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
@click="refetchStats()"
|
||||||
|
>
|
||||||
|
Opnieuw proberen
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
</VAlert>
|
||||||
|
<div
|
||||||
|
v-else-if="stats && stats.recent_activity.length === 0"
|
||||||
|
class="pa-6 text-center text-medium-emphasis"
|
||||||
|
>
|
||||||
|
Nog geen recente activiteit
|
||||||
|
</div>
|
||||||
|
<VList
|
||||||
|
v-else-if="stats"
|
||||||
|
lines="two"
|
||||||
|
density="comfortable"
|
||||||
|
>
|
||||||
|
<VListItem
|
||||||
|
v-for="entry in stats.recent_activity"
|
||||||
|
:key="entry.id"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VAvatar
|
||||||
|
:image="entry.causer_avatar_url ?? undefined"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="36"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="!entry.causer_avatar_url"
|
||||||
|
class="text-sm"
|
||||||
|
>{{ avatarInitials(entry.causer_name) }}</span>
|
||||||
|
</VAvatar>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<span class="text-body-1">{{ describeActivity(entry) }}</span>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<span class="text-body-2 text-medium-emphasis">
|
||||||
|
{{ entry.causer_name ?? 'Systeem' }} · {{ formatRelativeTime(entry.created_at) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
|
||||||
<EditOrganisationDialog
|
<EditOrganisationDialog
|
||||||
v-model="isEditDialogOpen"
|
v-model="isEditDialogOpen"
|
||||||
|
|||||||
Reference in New Issue
Block a user