f2f7bfc391
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
182 lines
5.5 KiB
Vue
182 lines
5.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
|
|
const props = defineProps<{
|
|
event: any | null
|
|
position: { x: number; y: number }
|
|
visible: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'click': [event: any]
|
|
}>()
|
|
|
|
const formatDateTime = (isoString: string | null | undefined): string => {
|
|
if (!isoString) return 'Not set'
|
|
const date = new Date(isoString)
|
|
return date.toLocaleString('en-US', {
|
|
weekday: 'short',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
hour: 'numeric',
|
|
minute: '2-digit'
|
|
})
|
|
}
|
|
|
|
const formatTime = (isoString: string | null | undefined): string => {
|
|
if (!isoString) return 'Not set'
|
|
const date = new Date(isoString)
|
|
return date.toLocaleTimeString('en-US', {
|
|
hour: 'numeric',
|
|
minute: '2-digit'
|
|
})
|
|
}
|
|
|
|
const popupStyle = computed(() => {
|
|
return {
|
|
left: `${props.position.x}px`,
|
|
top: `${props.position.y}px`
|
|
}
|
|
})
|
|
|
|
const hasLocation = computed(() => {
|
|
return props.event?.properties?.locationsPhysical && Object.keys(props.event.properties.locationsPhysical).length > 0 ||
|
|
props.event?.properties?.locationsVirtual && Object.keys(props.event.properties.locationsVirtual).length > 0
|
|
})
|
|
|
|
const firstLocation = computed(() => {
|
|
if (props.event?.properties?.locationsPhysical && Object.keys(props.event.properties.locationsPhysical).length > 0) {
|
|
const key = Object.keys(props.event.properties.locationsPhysical)[0]
|
|
return props.event.properties.locationsPhysical[key]
|
|
}
|
|
if (props.event?.properties?.locationsVirtual && Object.keys(props.event.properties.locationsVirtual).length > 0) {
|
|
const key = Object.keys(props.event.properties.locationsVirtual)[0]
|
|
return props.event.properties.locationsVirtual[key]
|
|
}
|
|
return null
|
|
})
|
|
|
|
const participantCount = computed(() => {
|
|
if (!props.event?.properties?.participants) return 0
|
|
return Object.keys(props.event.properties.participants).length
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<Transition name="fade">
|
|
<div
|
|
v-if="visible && event"
|
|
class="event-popup"
|
|
:style="popupStyle"
|
|
@click.stop="$emit('click', event)"
|
|
>
|
|
<v-card elevation="8" class="event-popup-card">
|
|
<v-card-text class="pa-3">
|
|
<div class="d-flex align-center mb-2">
|
|
<v-icon icon="mdi-calendar" size="small" class="mr-2" />
|
|
<div class="text-subtitle-1 font-weight-bold">{{ event.properties?.label || 'Untitled Event' }}</div>
|
|
</div>
|
|
|
|
<div class="event-popup-details">
|
|
<div class="d-flex align-center mb-1">
|
|
<v-icon icon="mdi-clock-outline" size="small" class="mr-2 text-grey" />
|
|
<div class="text-caption">
|
|
<div v-if="event.properties?.timeless">
|
|
All Day - {{ new Date(event.properties.startsOn).toLocaleDateString() }}
|
|
</div>
|
|
<div v-else>
|
|
{{ formatTime(event.properties?.startsOn) }} - {{ formatTime(event.properties?.endsOn) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="hasLocation" class="d-flex align-center mb-1">
|
|
<v-icon icon="mdi-map-marker" size="small" class="mr-2 text-grey" />
|
|
<div class="text-caption text-truncate">
|
|
{{ firstLocation?.label || firstLocation?.location || 'Location' }}
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="event.properties?.organizer" class="d-flex align-center mb-1">
|
|
<v-icon icon="mdi-account" size="small" class="mr-2 text-grey" />
|
|
<div class="text-caption">
|
|
{{ event.properties.organizer.name || event.properties.organizer.address }}
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="participantCount > 0" class="d-flex align-center mb-1">
|
|
<v-icon icon="mdi-account-group" size="small" class="mr-2 text-grey" />
|
|
<div class="text-caption">
|
|
{{ participantCount }} participant{{ participantCount !== 1 ? 's' : '' }}
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="event.properties?.description" class="mt-2 pt-2 border-t">
|
|
<div class="text-caption text-grey">{{ event.properties.description }}</div>
|
|
</div>
|
|
|
|
<div v-if="event.properties?.tags && event.properties.tags.length > 0" class="mt-2">
|
|
<v-chip
|
|
v-for="(tag, index) in event.properties.tags.slice(0, 3)"
|
|
:key="index"
|
|
size="x-small"
|
|
class="mr-1"
|
|
variant="tonal"
|
|
>
|
|
{{ tag }}
|
|
</v-chip>
|
|
<span v-if="event.properties.tags.length > 3" class="text-caption text-grey">
|
|
+{{ event.properties.tags.length - 3 }} more
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-caption text-grey-lighten-1 mt-2 text-center">
|
|
Click to view details
|
|
</div>
|
|
</v-card-text>
|
|
</v-card>
|
|
</div>
|
|
</Transition>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.event-popup {
|
|
position: fixed;
|
|
z-index: 9999;
|
|
pointer-events: auto;
|
|
transform: translate(10px, 10px);
|
|
}
|
|
|
|
.event-popup-card {
|
|
min-width: 280px;
|
|
max-width: 350px;
|
|
cursor: pointer;
|
|
transition: box-shadow 0.2s;
|
|
}
|
|
|
|
.event-popup-card:hover {
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2) !important;
|
|
}
|
|
|
|
.event-popup-details {
|
|
font-size: 13px;
|
|
}
|
|
|
|
.border-t {
|
|
border-top: 1px solid rgb(var(--v-border-color));
|
|
}
|
|
|
|
/* Fade transition */
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
</style>
|