refactor: organisation settings — vertical sidebar layout with grouped sections
Replace horizontal tabs with VList-based vertical sidebar following the Vuexy ecommerce settings pattern. Consolidate Tags, Crowd Types, Members, and Registration Fields pages into the settings page as sidebar tabs. Add SettingsGeneral panel with org details form and danger zone. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import CrowdTypesManager from '@/components/organisations/CrowdTypesManager.vue'
|
||||
|
||||
defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CrowdTypesManager :org-id="orgId" />
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import EmailBrandingTab from '@/components/organisation/EmailBrandingTab.vue'
|
||||
|
||||
defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EmailBrandingTab :org-id="orgId" />
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import EmailLogTab from '@/components/organisation/EmailLogTab.vue'
|
||||
|
||||
defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EmailLogTab :org-id="orgId" />
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import EmailTemplatesTab from '@/components/organisation/EmailTemplatesTab.vue'
|
||||
|
||||
defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EmailTemplatesTab :org-id="orgId" />
|
||||
</template>
|
||||
@@ -0,0 +1,321 @@
|
||||
<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>
|
||||
@@ -0,0 +1,484 @@
|
||||
<script setup lang="ts">
|
||||
import { useMemberList, useRemoveMember, useRevokeInvitation } from '@/composables/api/useMembers'
|
||||
import { useAdminChangeEmail } from '@/composables/api/useAccount'
|
||||
import { useAuthStore } from '@/stores/useAuthStore'
|
||||
import InviteMemberDialog from '@/components/members/InviteMemberDialog.vue'
|
||||
import EditMemberRoleDialog from '@/components/members/EditMemberRoleDialog.vue'
|
||||
import type { Member } from '@/types/member'
|
||||
|
||||
const props = defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const orgIdRef = computed(() => props.orgId)
|
||||
const orgName = computed(() => authStore.currentOrganisation?.name ?? '')
|
||||
|
||||
const isOrgAdmin = computed(() => {
|
||||
const role = authStore.currentOrganisation?.role
|
||||
return role === 'org_admin' || authStore.isSuperAdmin
|
||||
})
|
||||
|
||||
const { data: memberData, isLoading, isError, refetch } = useMemberList(orgIdRef)
|
||||
|
||||
const members = computed(() => memberData.value?.data ?? [])
|
||||
const pendingInvitations = computed(() => memberData.value?.meta?.pending_invitations ?? [])
|
||||
|
||||
// Invite dialog
|
||||
const isInviteDialogOpen = ref(false)
|
||||
|
||||
// Edit role dialog
|
||||
const isEditRoleDialogOpen = ref(false)
|
||||
const selectedMember = ref<Member | null>(null)
|
||||
|
||||
// Remove member
|
||||
const isRemoveDialogOpen = ref(false)
|
||||
const memberToRemove = ref<Member | null>(null)
|
||||
const { mutate: removeMember, isPending: isRemoving } = useRemoveMember(orgIdRef)
|
||||
|
||||
// Revoke invitation
|
||||
const isRevokeDialogOpen = ref(false)
|
||||
const invitationToRevoke = ref<{ id: string; email: string } | null>(null)
|
||||
const { mutate: revokeInvitation, isPending: isRevoking } = useRevokeInvitation(orgIdRef)
|
||||
|
||||
// Change email
|
||||
const isEmailChangeDialogOpen = ref(false)
|
||||
const memberToChangeEmail = ref<Member | null>(null)
|
||||
const newMemberEmail = ref('')
|
||||
const adminEmailErrors = ref<Record<string, string>>({})
|
||||
const showEmailChangeSuccess = ref(false)
|
||||
const { mutate: adminChangeEmail, isPending: isChangingMemberEmail } = useAdminChangeEmail(orgIdRef)
|
||||
|
||||
const showRemoveSuccess = ref(false)
|
||||
const showRevokeSuccess = ref(false)
|
||||
|
||||
const roleColorMap: Record<string, string> = {
|
||||
org_admin: 'purple',
|
||||
org_member: 'info',
|
||||
event_manager: 'cyan',
|
||||
staff_coordinator: 'orange',
|
||||
volunteer_coordinator: 'success',
|
||||
}
|
||||
|
||||
const roleLabelMap: Record<string, string> = {
|
||||
org_admin: 'Organisatie Beheerder',
|
||||
org_member: 'Organisatie Lid',
|
||||
event_manager: 'Evenement Manager',
|
||||
staff_coordinator: 'Staf Coördinator',
|
||||
volunteer_coordinator: 'Vrijwilliger Coördinator',
|
||||
}
|
||||
|
||||
const memberSearch = ref('')
|
||||
|
||||
const memberHeaders = computed(() => {
|
||||
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, align: 'end' })
|
||||
}
|
||||
return headers
|
||||
})
|
||||
|
||||
const memberSortBy = [{ key: 'full_name', order: 'asc' as const }]
|
||||
|
||||
function getInitials(name: string): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.map(p => p[0])
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
const d = new Date(iso)
|
||||
const date = d.toLocaleDateString('nl-NL', { day: 'numeric', month: 'long', year: 'numeric' })
|
||||
const time = d.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' })
|
||||
return `${date} om ${time} uur`
|
||||
}
|
||||
|
||||
function openEditRole(member: Member) {
|
||||
selectedMember.value = member
|
||||
isEditRoleDialogOpen.value = true
|
||||
}
|
||||
|
||||
function openRemoveDialog(member: Member) {
|
||||
memberToRemove.value = member
|
||||
isRemoveDialogOpen.value = true
|
||||
}
|
||||
|
||||
function confirmRemoveMember() {
|
||||
if (!memberToRemove.value) return
|
||||
|
||||
removeMember(memberToRemove.value.id, {
|
||||
onSuccess: () => {
|
||||
isRemoveDialogOpen.value = false
|
||||
memberToRemove.value = null
|
||||
showRemoveSuccess.value = true
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function openRevokeDialog(invitation: { id: string; email: string }) {
|
||||
invitationToRevoke.value = invitation
|
||||
isRevokeDialogOpen.value = true
|
||||
}
|
||||
|
||||
function openEmailChangeDialog(member: Member) {
|
||||
memberToChangeEmail.value = member
|
||||
newMemberEmail.value = ''
|
||||
adminEmailErrors.value = {}
|
||||
isEmailChangeDialogOpen.value = true
|
||||
}
|
||||
|
||||
function confirmEmailChange() {
|
||||
if (!memberToChangeEmail.value) return
|
||||
adminEmailErrors.value = {}
|
||||
|
||||
adminChangeEmail(
|
||||
{ userId: memberToChangeEmail.value.id, newEmail: newMemberEmail.value },
|
||||
{
|
||||
onSuccess: () => {
|
||||
isEmailChangeDialogOpen.value = false
|
||||
memberToChangeEmail.value = null
|
||||
newMemberEmail.value = ''
|
||||
showEmailChangeSuccess.value = true
|
||||
},
|
||||
onError: (err: unknown) => {
|
||||
const ax = err as { response?: { data?: { errors?: Record<string, string[]> } } }
|
||||
if (ax.response?.data?.errors) {
|
||||
for (const [key, messages] of Object.entries(ax.response.data.errors)) {
|
||||
adminEmailErrors.value[key] = messages[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function confirmRevokeInvitation() {
|
||||
if (!invitationToRevoke.value) return
|
||||
|
||||
revokeInvitation(invitationToRevoke.value.id, {
|
||||
onSuccess: () => {
|
||||
isRevokeDialogOpen.value = false
|
||||
invitationToRevoke.value = null
|
||||
showRevokeSuccess.value = true
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Loading -->
|
||||
<VSkeletonLoader
|
||||
v-if="isLoading"
|
||||
type="card"
|
||||
/>
|
||||
|
||||
<!-- Error -->
|
||||
<VAlert
|
||||
v-else-if="isError"
|
||||
type="error"
|
||||
class="mb-4"
|
||||
>
|
||||
Kon leden niet laden.
|
||||
<template #append>
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="refetch()"
|
||||
>
|
||||
Opnieuw proberen
|
||||
</VBtn>
|
||||
</template>
|
||||
</VAlert>
|
||||
|
||||
<template v-else>
|
||||
<!-- Members table -->
|
||||
<VCard class="mb-6">
|
||||
<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 align-center"
|
||||
>
|
||||
<VBtn
|
||||
prepend-icon="tabler-user-plus"
|
||||
@click="isInviteDialogOpen = true"
|
||||
>
|
||||
Lid uitnodigen
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
<VDataTable
|
||||
:headers="memberHeaders"
|
||||
:items="members"
|
||||
:search="memberSearch"
|
||||
:sort-by="memberSortBy"
|
||||
:items-per-page="10"
|
||||
>
|
||||
<template #item.full_name="{ item }">
|
||||
<div class="d-flex align-center gap-x-3">
|
||||
<VAvatar
|
||||
v-if="item.avatar"
|
||||
size="34"
|
||||
:image="item.avatar"
|
||||
/>
|
||||
<VAvatar
|
||||
v-else
|
||||
size="34"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<span class="text-sm">{{ getInitials(item.full_name) }}</span>
|
||||
</VAvatar>
|
||||
<span>{{ item.full_name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.role="{ item }">
|
||||
<VChip
|
||||
:color="roleColorMap[item.role] ?? 'default'"
|
||||
size="small"
|
||||
>
|
||||
{{ roleLabelMap[item.role] ?? item.role }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<template #item.actions="{ item }">
|
||||
<div class="d-flex gap-x-1 justify-end">
|
||||
<VBtn
|
||||
icon="tabler-edit"
|
||||
variant="text"
|
||||
size="small"
|
||||
@click="openEditRole(item)"
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-mail-forward"
|
||||
variant="text"
|
||||
size="small"
|
||||
@click="openEmailChangeDialog(item)"
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
@click="openRemoveDialog(item)"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<!-- Pending invitations -->
|
||||
<VCard
|
||||
v-if="isOrgAdmin && pendingInvitations.length > 0"
|
||||
>
|
||||
<VCardTitle>Openstaande uitnodigingen</VCardTitle>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="invitation in pendingInvitations"
|
||||
:key="invitation.id"
|
||||
>
|
||||
<template #prepend>
|
||||
<VAvatar
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
size="34"
|
||||
>
|
||||
<VIcon icon="tabler-mail" />
|
||||
</VAvatar>
|
||||
</template>
|
||||
|
||||
<VListItemTitle>
|
||||
{{ invitation.email }}
|
||||
<VChip
|
||||
:color="roleColorMap[invitation.role] ?? 'default'"
|
||||
size="x-small"
|
||||
class="ms-2"
|
||||
>
|
||||
{{ roleLabelMap[invitation.role] ?? invitation.role }}
|
||||
</VChip>
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle>
|
||||
Verloopt op {{ formatDate(invitation.expires_at) }}
|
||||
</VListItemSubtitle>
|
||||
|
||||
<template #append>
|
||||
<VBtn
|
||||
variant="text"
|
||||
color="error"
|
||||
size="small"
|
||||
@click="openRevokeDialog(invitation)"
|
||||
>
|
||||
Intrekken
|
||||
</VBtn>
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<InviteMemberDialog
|
||||
v-if="isOrgAdmin"
|
||||
v-model="isInviteDialogOpen"
|
||||
:org-id="orgId"
|
||||
/>
|
||||
|
||||
<EditMemberRoleDialog
|
||||
v-if="selectedMember"
|
||||
v-model="isEditRoleDialogOpen"
|
||||
:org-id="orgId"
|
||||
:member="selectedMember"
|
||||
/>
|
||||
|
||||
<!-- Remove member confirmation -->
|
||||
<VDialog
|
||||
v-model="isRemoveDialogOpen"
|
||||
max-width="400"
|
||||
>
|
||||
<VCard title="Lid verwijderen">
|
||||
<VCardText>
|
||||
Weet je zeker dat je <strong>{{ memberToRemove?.full_name }}</strong> wilt verwijderen uit de organisatie?
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="isRemoveDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="error"
|
||||
:loading="isRemoving"
|
||||
@click="confirmRemoveMember"
|
||||
>
|
||||
Verwijderen
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Revoke invitation confirmation -->
|
||||
<VDialog
|
||||
v-model="isRevokeDialogOpen"
|
||||
max-width="400"
|
||||
>
|
||||
<VCard title="Uitnodiging intrekken">
|
||||
<VCardText>
|
||||
Weet je zeker dat je de uitnodiging voor <strong>{{ invitationToRevoke?.email }}</strong> wilt intrekken?
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="isRevokeDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="error"
|
||||
:loading="isRevoking"
|
||||
@click="confirmRevokeInvitation"
|
||||
>
|
||||
Intrekken
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Change email dialog -->
|
||||
<VDialog
|
||||
v-model="isEmailChangeDialogOpen"
|
||||
max-width="420"
|
||||
>
|
||||
<VCard title="E-mailadres wijzigen">
|
||||
<VCardText>
|
||||
<p class="text-body-2 text-medium-emphasis mb-4">
|
||||
Wijzig het e-mailadres van <strong>{{ memberToChangeEmail?.full_name }}</strong>.
|
||||
Er wordt een verificatiemail verstuurd naar het nieuwe adres.
|
||||
</p>
|
||||
<AppTextField
|
||||
v-model="newMemberEmail"
|
||||
label="Nieuw e-mailadres"
|
||||
type="email"
|
||||
:error-messages="adminEmailErrors.new_email"
|
||||
/>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
@click="isEmailChangeDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
:loading="isChangingMemberEmail"
|
||||
:disabled="!newMemberEmail"
|
||||
@click="confirmEmailChange"
|
||||
>
|
||||
Verificatiemail versturen
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Success snackbars -->
|
||||
<VSnackbar
|
||||
v-model="showRemoveSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Lid verwijderd
|
||||
</VSnackbar>
|
||||
<VSnackbar
|
||||
v-model="showRevokeSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Uitnodiging ingetrokken
|
||||
</VSnackbar>
|
||||
<VSnackbar
|
||||
v-model="showEmailChangeSuccess"
|
||||
color="success"
|
||||
:timeout="4000"
|
||||
>
|
||||
Verificatiemail verstuurd
|
||||
</VSnackbar>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import RegistrationFieldTemplatesTab from '@/components/organisation/RegistrationFieldTemplatesTab.vue'
|
||||
|
||||
defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RegistrationFieldTemplatesTab :org-id="orgId" />
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import PersonTagsTab from '@/components/organisation/PersonTagsTab.vue'
|
||||
|
||||
defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PersonTagsTab :org-id="orgId" />
|
||||
</template>
|
||||
Reference in New Issue
Block a user