refactor(app): event header status menu and volunteer share dialog
Replace separate status chips/buttons with one status dropdown next to edit, move dates under the title, add share dialog for registration URL, and remove RegistrationLinkCard. Made-with: Cursor
This commit is contained in:
1
apps/app/components.d.ts
vendored
1
apps/app/components.d.ts
vendored
@@ -77,7 +77,6 @@ declare module 'vue' {
|
|||||||
RegistrationFieldCard: typeof import('./src/components/event/RegistrationFieldCard.vue')['default']
|
RegistrationFieldCard: typeof import('./src/components/event/RegistrationFieldCard.vue')['default']
|
||||||
RegistrationFieldFormDialog: typeof import('./src/components/event/RegistrationFieldFormDialog.vue')['default']
|
RegistrationFieldFormDialog: typeof import('./src/components/event/RegistrationFieldFormDialog.vue')['default']
|
||||||
RegistrationFieldTemplatesTab: typeof import('./src/components/organisation/RegistrationFieldTemplatesTab.vue')['default']
|
RegistrationFieldTemplatesTab: typeof import('./src/components/organisation/RegistrationFieldTemplatesTab.vue')['default']
|
||||||
RegistrationLinkCard: typeof import('./src/components/events/RegistrationLinkCard.vue')['default']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
ScrollToTop: typeof import('./src/@core/components/ScrollToTop.vue')['default']
|
ScrollToTop: typeof import('./src/@core/components/ScrollToTop.vue')['default']
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useAuthStore } from '@/stores/useAuthStore'
|
|||||||
import { useNotificationStore } from '@/stores/useNotificationStore'
|
import { useNotificationStore } from '@/stores/useNotificationStore'
|
||||||
import { useOrganisationStore } from '@/stores/useOrganisationStore'
|
import { useOrganisationStore } from '@/stores/useOrganisationStore'
|
||||||
import EditEventDialog from '@/components/events/EditEventDialog.vue'
|
import EditEventDialog from '@/components/events/EditEventDialog.vue'
|
||||||
import RegistrationLinkCard from '@/components/events/RegistrationLinkCard.vue'
|
|
||||||
import type { EventStatus } from '@/types/event'
|
import type { EventStatus } from '@/types/event'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -39,10 +38,6 @@ function transitionButtonColor(current: EventStatus, target: EventStatus): strin
|
|||||||
return 'primary'
|
return 'primary'
|
||||||
}
|
}
|
||||||
|
|
||||||
function transitionButtonVariant(current: EventStatus, target: EventStatus): 'flat' | 'outlined' {
|
|
||||||
return transitionVisualKind(current, target) === 'backward' ? 'outlined' : 'flat'
|
|
||||||
}
|
|
||||||
|
|
||||||
function openTransitionConfirm(target: EventStatus) {
|
function openTransitionConfirm(target: EventStatus) {
|
||||||
pendingTransitionStatus.value = target
|
pendingTransitionStatus.value = target
|
||||||
confirmTransitionOpen.value = true
|
confirmTransitionOpen.value = true
|
||||||
@@ -96,9 +91,23 @@ watch(eventId, (id) => {
|
|||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
const isEditDialogOpen = ref(false)
|
const isEditDialogOpen = ref(false)
|
||||||
|
const statusMenuOpen = ref(false)
|
||||||
|
const shareDialogOpen = ref(false)
|
||||||
|
const linkCopied = ref(false)
|
||||||
|
|
||||||
const statusColor: Record<EventStatus, string> = {
|
const portalBaseUrl = import.meta.env.VITE_PORTAL_URL || 'https://portal.crewli.app'
|
||||||
draft: 'default',
|
|
||||||
|
const registrationUrl = computed(() =>
|
||||||
|
event.value ? `${portalBaseUrl}/register/${event.value.slug}` : '',
|
||||||
|
)
|
||||||
|
|
||||||
|
const isRegistrationOpen = computed(() => event.value?.status === 'registration_open')
|
||||||
|
|
||||||
|
const showVolunteerShare = computed(() => Boolean(event.value && !event.value.parent_event_id))
|
||||||
|
|
||||||
|
/** Kleuren voor statusknop / icoon (VBtn; concept = secondary i.p.v. chip-default). */
|
||||||
|
const statusActionColor: Record<EventStatus, string> = {
|
||||||
|
draft: 'secondary',
|
||||||
published: 'info',
|
published: 'info',
|
||||||
registration_open: 'cyan',
|
registration_open: 'cyan',
|
||||||
buildup: 'warning',
|
buildup: 'warning',
|
||||||
@@ -122,6 +131,21 @@ function formatDate(iso: string) {
|
|||||||
return dateFormatter.format(new Date(iso))
|
return dateFormatter.format(new Date(iso))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSelectTransition(target: EventStatus) {
|
||||||
|
statusMenuOpen.value = false
|
||||||
|
openTransitionConfirm(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyRegistrationLink() {
|
||||||
|
if (!registrationUrl.value)
|
||||||
|
return
|
||||||
|
await navigator.clipboard.writeText(registrationUrl.value)
|
||||||
|
linkCopied.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
linkCopied.value = false
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
const baseTabs = [
|
const baseTabs = [
|
||||||
{ label: 'Overzicht', icon: 'tabler-layout-dashboard', route: 'events-id' },
|
{ label: 'Overzicht', icon: 'tabler-layout-dashboard', route: 'events-id' },
|
||||||
{ label: 'Personen', icon: 'tabler-users', route: 'events-id-persons' },
|
{ label: 'Personen', icon: 'tabler-users', route: 'events-id-persons' },
|
||||||
@@ -204,65 +228,106 @@ const backRoute = computed(() => {
|
|||||||
|
|
||||||
<template v-else-if="event">
|
<template v-else-if="event">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="d-flex flex-wrap justify-space-between align-center gap-y-4 mb-6">
|
<div class="d-flex flex-wrap justify-space-between align-start gap-y-4 gap-x-4 mb-6">
|
||||||
<div class="d-flex flex-wrap align-center gap-x-3 gap-y-2">
|
<div class="d-flex align-start gap-x-3 min-w-0">
|
||||||
<VBtn
|
<VBtn
|
||||||
icon="tabler-arrow-left"
|
icon="tabler-arrow-left"
|
||||||
variant="text"
|
variant="text"
|
||||||
|
class="mt-1 flex-shrink-0"
|
||||||
:to="backRoute"
|
:to="backRoute"
|
||||||
/>
|
/>
|
||||||
<h4 class="text-h4">
|
<div class="min-w-0">
|
||||||
<template v-if="event.is_sub_event && event.parent && event.parent_event_id">
|
<div class="d-flex flex-wrap align-center gap-x-2 gap-y-1">
|
||||||
<RouterLink
|
<h4 class="text-h4 mb-0">
|
||||||
:to="{ name: 'events-id', params: { id: event.parent_event_id } }"
|
<template v-if="event.is_sub_event && event.parent && event.parent_event_id">
|
||||||
class="text-medium-emphasis text-decoration-none"
|
<RouterLink
|
||||||
|
:to="{ name: 'events-id', params: { id: event.parent_event_id } }"
|
||||||
|
class="text-medium-emphasis text-decoration-none"
|
||||||
|
>
|
||||||
|
{{ event.parent.name }}
|
||||||
|
</RouterLink>
|
||||||
|
<span class="text-medium-emphasis mx-1">»</span>
|
||||||
|
</template>
|
||||||
|
{{ event.name }}
|
||||||
|
</h4>
|
||||||
|
<VChip
|
||||||
|
v-if="event.event_type === 'festival' || event.event_type === 'series'"
|
||||||
|
:color="eventTypeColor[event.event_type]"
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="flex-shrink-0"
|
||||||
>
|
>
|
||||||
{{ event.parent.name }}
|
{{ event.event_type_label ?? (event.event_type === 'festival' ? 'Festival' : 'Serie') }}
|
||||||
</RouterLink>
|
</VChip>
|
||||||
<span class="text-medium-emphasis mx-1">»</span>
|
</div>
|
||||||
</template>
|
<div class="text-body-1 text-medium-emphasis mt-2">
|
||||||
{{ event.name }}
|
{{ formatDate(event.start_date) }} – {{ formatDate(event.end_date) }}
|
||||||
</h4>
|
</div>
|
||||||
<VChip
|
|
||||||
v-if="event.event_type === 'festival' || event.event_type === 'series'"
|
|
||||||
:color="eventTypeColor[event.event_type]"
|
|
||||||
size="small"
|
|
||||||
variant="tonal"
|
|
||||||
>
|
|
||||||
{{ event.event_type_label ?? (event.event_type === 'festival' ? 'Festival' : 'Serie') }}
|
|
||||||
</VChip>
|
|
||||||
<VChip
|
|
||||||
:color="statusColor[event.status]"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ eventStatusLabelNl(event.status) }}
|
|
||||||
</VChip>
|
|
||||||
<div
|
|
||||||
v-if="allowedTransitions.length"
|
|
||||||
class="d-flex flex-wrap align-center gap-2"
|
|
||||||
>
|
|
||||||
<VBtn
|
|
||||||
v-for="target in allowedTransitions"
|
|
||||||
:key="target"
|
|
||||||
size="small"
|
|
||||||
:color="transitionButtonColor(event.status, target)"
|
|
||||||
:variant="transitionButtonVariant(event.status, target)"
|
|
||||||
:disabled="isTransitionPending"
|
|
||||||
@click="openTransitionConfirm(target)"
|
|
||||||
>
|
|
||||||
{{ eventStatusLabelNl(target) }}
|
|
||||||
</VBtn>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="text-body-1 text-disabled">
|
|
||||||
{{ formatDate(event.start_date) }} – {{ formatDate(event.end_date) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<VBtn
|
<div class="d-flex align-center gap-2 flex-shrink-0 ms-auto">
|
||||||
prepend-icon="tabler-edit"
|
<VTooltip
|
||||||
@click="isEditDialogOpen = true"
|
v-if="showVolunteerShare"
|
||||||
>
|
:disabled="isRegistrationOpen"
|
||||||
Bewerken
|
location="top"
|
||||||
</VBtn>
|
max-width="320"
|
||||||
|
>
|
||||||
|
<template #activator="{ props: tooltipProps }">
|
||||||
|
<span v-bind="tooltipProps">
|
||||||
|
<VBtn
|
||||||
|
prepend-icon="tabler-share-3"
|
||||||
|
variant="outlined"
|
||||||
|
:disabled="!isRegistrationOpen"
|
||||||
|
@click="shareDialogOpen = true"
|
||||||
|
>
|
||||||
|
Delen
|
||||||
|
</VBtn>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
Zet de status op 'Registratie open' om het formulier te activeren.
|
||||||
|
</VTooltip>
|
||||||
|
<VMenu
|
||||||
|
v-model="statusMenuOpen"
|
||||||
|
location="bottom end"
|
||||||
|
>
|
||||||
|
<template #activator="{ props: menuProps }">
|
||||||
|
<VBtn
|
||||||
|
v-bind="menuProps"
|
||||||
|
:color="statusActionColor[event.status]"
|
||||||
|
:variant="allowedTransitions.length ? 'flat' : 'tonal'"
|
||||||
|
:disabled="!allowedTransitions.length || isTransitionPending"
|
||||||
|
append-icon="tabler-chevron-down"
|
||||||
|
>
|
||||||
|
{{ eventStatusLabelNl(event.status) }}
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
<VList
|
||||||
|
density="compact"
|
||||||
|
class="py-1"
|
||||||
|
>
|
||||||
|
<VListItem
|
||||||
|
v-for="target in allowedTransitions"
|
||||||
|
:key="target"
|
||||||
|
:title="eventStatusLabelNl(target)"
|
||||||
|
@click="onSelectTransition(target)"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon
|
||||||
|
:icon="transitionVisualKind(event.status, target) === 'dangerous' ? 'tabler-alert-triangle' : 'tabler-arrow-right'"
|
||||||
|
:color="transitionButtonColor(event.status, target)"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VMenu>
|
||||||
|
<VBtn
|
||||||
|
prepend-icon="tabler-edit"
|
||||||
|
@click="isEditDialogOpen = true"
|
||||||
|
>
|
||||||
|
Bewerken
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VDialog
|
<VDialog
|
||||||
@@ -297,11 +362,57 @@ const backRoute = computed(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
|
|
||||||
<!-- Registration link (top-level events only) -->
|
<VDialog
|
||||||
<RegistrationLinkCard
|
v-if="showVolunteerShare"
|
||||||
v-if="!event.parent_event_id"
|
v-model="shareDialogOpen"
|
||||||
:event="event"
|
max-width="520"
|
||||||
/>
|
>
|
||||||
|
<VCard>
|
||||||
|
<VCardTitle class="text-h6">
|
||||||
|
Vrijwilligersformulier delen
|
||||||
|
</VCardTitle>
|
||||||
|
<VCardText>
|
||||||
|
<p class="text-body-2 text-medium-emphasis mb-4">
|
||||||
|
Deel deze link met vrijwilligers om zich aan te melden.
|
||||||
|
</p>
|
||||||
|
<VTextField
|
||||||
|
:model-value="registrationUrl"
|
||||||
|
readonly
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
hide-details
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
<div class="d-flex flex-wrap gap-2">
|
||||||
|
<VBtn
|
||||||
|
variant="outlined"
|
||||||
|
prepend-icon="tabler-copy"
|
||||||
|
@click="copyRegistrationLink"
|
||||||
|
>
|
||||||
|
{{ linkCopied ? 'Gekopieerd!' : 'Kopieer link' }}
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-external-link"
|
||||||
|
:href="registrationUrl"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Open formulier
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
<VCardActions>
|
||||||
|
<VSpacer />
|
||||||
|
<VBtn
|
||||||
|
variant="text"
|
||||||
|
@click="shareDialogOpen = false"
|
||||||
|
>
|
||||||
|
Sluiten
|
||||||
|
</VBtn>
|
||||||
|
</VCardActions>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
|
|
||||||
<!-- Horizontal tabs -->
|
<!-- Horizontal tabs -->
|
||||||
<VTabs
|
<VTabs
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { EventItem } from '@/types/event'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
event: EventItem
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const portalBaseUrl = import.meta.env.VITE_PORTAL_URL || 'https://portal.crewli.app'
|
|
||||||
|
|
||||||
const registrationUrl = computed(() =>
|
|
||||||
`${portalBaseUrl}/register/${props.event.slug}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
const isRegistrationOpen = computed(() =>
|
|
||||||
props.event.status === 'registration_open',
|
|
||||||
)
|
|
||||||
|
|
||||||
const copied = ref(false)
|
|
||||||
|
|
||||||
async function copyLink() {
|
|
||||||
await navigator.clipboard.writeText(registrationUrl.value)
|
|
||||||
copied.value = true
|
|
||||||
setTimeout(() => { copied.value = false }, 2000)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<VCard
|
|
||||||
v-if="event"
|
|
||||||
variant="outlined"
|
|
||||||
class="mb-4"
|
|
||||||
>
|
|
||||||
<VCardText>
|
|
||||||
<div class="d-flex align-center justify-space-between flex-wrap ga-2">
|
|
||||||
<div>
|
|
||||||
<div class="text-subtitle-2 text-medium-emphasis mb-1">
|
|
||||||
Vrijwilligersregistratie
|
|
||||||
</div>
|
|
||||||
<template v-if="isRegistrationOpen">
|
|
||||||
<code class="text-body-2">{{ registrationUrl }}</code>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="text-body-2 text-medium-emphasis">
|
|
||||||
Zet de status op 'Registratie open' om het formulier te activeren.
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="isRegistrationOpen"
|
|
||||||
class="d-flex ga-2"
|
|
||||||
>
|
|
||||||
<VBtn
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
prepend-icon="tabler-copy"
|
|
||||||
@click="copyLink"
|
|
||||||
>
|
|
||||||
{{ copied ? 'Gekopieerd!' : 'Kopieer link' }}
|
|
||||||
</VBtn>
|
|
||||||
<VBtn
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
prepend-icon="tabler-external-link"
|
|
||||||
:href="registrationUrl"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Open formulier
|
|
||||||
</VBtn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
</template>
|
|
||||||
Reference in New Issue
Block a user