feat: build the occurrence index
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -27,19 +27,19 @@
|
||||
<v-list-item-title class="text-center text-medium-emphasis">No events in this period</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-for="(dayEvents, date) in groupedEvents" :key="date">
|
||||
<template v-for="(dayOccurrences, date) in groupedEvents" :key="date">
|
||||
<v-list-subheader>{{ formatAgendaDate(date) }}</v-list-subheader>
|
||||
<v-list-item
|
||||
v-for="entity in dayEvents"
|
||||
:key="entity.identifier"
|
||||
@click="$emit('event-click', entity)"
|
||||
v-for="occurrence in dayOccurrences"
|
||||
:key="occurrence.key"
|
||||
@click="$emit('event-click', occurrence.entity)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon :color="getEventColor(entity)">mdi-circle</v-icon>
|
||||
<v-icon :color="getEventColor(occurrence)">mdi-circle</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ entity.properties?.label || 'Untitled' }}</v-list-item-title>
|
||||
<v-list-item-title>{{ occurrence.entity.properties?.label || 'Untitled' }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ getEventProperties(entity).timeless ? 'All day' : `${formatEventDateTime(getEventProperties(entity).startsOn)} - ${formatEventDateTime(getEventProperties(entity).endsOn)}` }}
|
||||
{{ occurrence.timeless ? 'All day' : `${formatOccurrenceTime(occurrence.startMs)} - ${formatOccurrenceTime(occurrence.endMs)}` }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</template>
|
||||
@@ -54,12 +54,14 @@ import type { EntityObject } from '@ChronoManager/models/entity';
|
||||
import type { CollectionObject } from '@ChronoManager/models/collection';
|
||||
import { EventObject } from '@ChronoManager/models/event';
|
||||
import { daysVisibleRange, shiftLocalDays } from '@/utils/dateRanges';
|
||||
import { occurrencesForRange } from '@/utils/occurrenceIndex';
|
||||
import type { CalendarOccurrence, CalendarOccurrenceIndex } from '@/types/occurrence';
|
||||
|
||||
type CalendarEntity = EntityObject;
|
||||
type CalendarCollection = CollectionObject;
|
||||
|
||||
const props = defineProps<{
|
||||
events: CalendarEntity[];
|
||||
occurrenceIndex: CalendarOccurrenceIndex;
|
||||
calendars: CalendarCollection[];
|
||||
currentDate: Date;
|
||||
initialSpan?: AgendaViewSpan;
|
||||
@@ -126,32 +128,27 @@ const dateRangeLabel = computed(() => {
|
||||
});
|
||||
|
||||
const groupedEvents = computed(() => {
|
||||
const grouped: Record<string, CalendarEntity[]> = {};
|
||||
const grouped: Record<string, CalendarOccurrence[]> = {};
|
||||
const { start, end } = dateRange.value;
|
||||
|
||||
const filtered = props.events.filter((e): e is CalendarEntity => {
|
||||
const startsOn = getEventProperties(e).startsOn;
|
||||
if (typeof startsOn !== 'string') return false;
|
||||
const eventStart = new Date(startsOn);
|
||||
return eventStart >= start && eventStart < end;
|
||||
|
||||
const occurrences = occurrencesForRange(props.occurrenceIndex, {
|
||||
startMs: start.getTime(),
|
||||
endMs: end.getTime(),
|
||||
});
|
||||
|
||||
const sorted = filtered.sort((a, b) =>
|
||||
new Date(getEventProperties(a).startsOn || 0).getTime() - new Date(getEventProperties(b).startsOn || 0).getTime()
|
||||
);
|
||||
|
||||
sorted.forEach(entity => {
|
||||
const dateKey = new Date(getEventProperties(entity).startsOn || 0).toDateString();
|
||||
|
||||
occurrences.forEach(occurrence => {
|
||||
const dateKey = new Date(occurrence.startMs).toDateString();
|
||||
if (!grouped[dateKey]) {
|
||||
grouped[dateKey] = [];
|
||||
}
|
||||
grouped[dateKey].push(entity);
|
||||
grouped[dateKey].push(occurrence);
|
||||
});
|
||||
|
||||
return grouped;
|
||||
});
|
||||
|
||||
function getEventColor(entity: CalendarEntity): string {
|
||||
function getEventColor(occurrence: CalendarOccurrence): string {
|
||||
const entity = occurrence.entity;
|
||||
const event = getEventProperties(entity);
|
||||
if (event.color) return event.color;
|
||||
const calendar = props.calendars.find(cal => cal.identifier === entity.collection);
|
||||
@@ -166,8 +163,8 @@ function formatTime(date: Date): string {
|
||||
return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function formatEventDateTime(value: string | null): string {
|
||||
return value ? formatTime(new Date(value)) : '';
|
||||
function formatOccurrenceTime(timestamp: number): string {
|
||||
return formatTime(new Date(timestamp));
|
||||
}
|
||||
|
||||
function formatAgendaDate(dateString: string): string {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<MonthView
|
||||
v-if="view === 'month'"
|
||||
:current-date="currentDate"
|
||||
:events="events"
|
||||
:occurrence-index="occurrenceIndex"
|
||||
:calendars="calendars"
|
||||
@event-click="$emit('event-click', $event)"
|
||||
@date-click="$emit('date-click', $event)"
|
||||
@@ -20,7 +20,7 @@
|
||||
<DaysView
|
||||
v-else-if="view === 'days'"
|
||||
:current-date="currentDate"
|
||||
:events="events"
|
||||
:occurrence-index="occurrenceIndex"
|
||||
:calendars="calendars"
|
||||
:initial-span="initialDaysSpan"
|
||||
@event-click="$emit('event-click', $event)"
|
||||
@@ -32,7 +32,7 @@
|
||||
/>
|
||||
<AgendaView
|
||||
v-else-if="view === 'agenda'"
|
||||
:events="events"
|
||||
:occurrence-index="occurrenceIndex"
|
||||
:calendars="calendars"
|
||||
:current-date="currentDate"
|
||||
:initial-span="initialAgendaViewSpan"
|
||||
@@ -53,12 +53,13 @@ import { spanToDays, type AgendaViewSpan, type DaysViewSpan } from '@/types/span
|
||||
import type { EntityObject } from '@ChronoManager/models/entity';
|
||||
import type { CollectionObject } from '@ChronoManager/models/collection';
|
||||
import type { VisibleDateRange } from '@/types';
|
||||
import type { CalendarOccurrenceIndex } from '@/types/occurrence';
|
||||
import { daysVisibleRange, monthGridVisibleRange } from '@/utils/dateRanges';
|
||||
|
||||
const props = defineProps<{
|
||||
view: 'days' | 'month' | 'agenda';
|
||||
currentDate: Date;
|
||||
events: EntityObject[];
|
||||
occurrenceIndex: CalendarOccurrenceIndex;
|
||||
calendars: CollectionObject[];
|
||||
initialDaysSpan?: DaysViewSpan;
|
||||
initialAgendaViewSpan?: AgendaViewSpan;
|
||||
|
||||
+30
-30
@@ -45,18 +45,18 @@
|
||||
<div class="all-day-events-overlay">
|
||||
<div
|
||||
v-for="segment in allDaySegments.segments"
|
||||
:key="segment.entity.identifier"
|
||||
:key="segment.occurrence.key"
|
||||
class="all-day-event"
|
||||
:class="{
|
||||
'is-start': segment.isStart,
|
||||
'is-end': segment.isEnd,
|
||||
}"
|
||||
:style="getAllDayEventStyle(segment)"
|
||||
@click="emit('event-click', segment.entity)"
|
||||
@mouseenter="emit('event-hover', { event: $event, entity: segment.entity })"
|
||||
@click="emit('event-click', segment.occurrence.entity)"
|
||||
@mouseenter="emit('event-hover', { event: $event, entity: segment.occurrence.entity })"
|
||||
@mouseleave="emit('event-hover-end')"
|
||||
>
|
||||
<span v-if="segment.isStart" class="event-label">{{ segment.entity.properties?.label || 'Untitled' }}</span>
|
||||
<span v-if="segment.isStart" class="event-label">{{ segment.occurrence.entity.properties?.label || 'Untitled' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,22 +73,22 @@
|
||||
<div v-for="day in visibleDates" :key="day.toISOString()" class="day-column" :class="{ 'single-day': daysCount === 1 }">
|
||||
<div class="day-events" @click="handleDayClick($event, day)">
|
||||
<div
|
||||
v-for="entity in getTimedEvents(day)"
|
||||
:key="entity.identifier"
|
||||
v-for="occurrence in getTimedEvents(day)"
|
||||
:key="occurrence.key"
|
||||
class="day-event"
|
||||
:style="getEventStyle(entity)"
|
||||
@click.stop="emit('event-click', entity)"
|
||||
@mouseenter="emit('event-hover', { event: $event, entity })"
|
||||
:style="getEventStyle(occurrence)"
|
||||
@click.stop="emit('event-click', occurrence.entity)"
|
||||
@mouseenter="emit('event-hover', { event: $event, entity: occurrence.entity })"
|
||||
@mouseleave="emit('event-hover-end')"
|
||||
>
|
||||
<template v-if="daysCount <= 3">
|
||||
<div class="event-time">
|
||||
{{ formatEventDateTime(getEventProperties(entity).startsOn) }} - {{ formatEventDateTime(getEventProperties(entity).endsOn) }}
|
||||
{{ formatOccurrenceTime(occurrence.startMs) }} - {{ formatOccurrenceTime(occurrence.endMs) }}
|
||||
</div>
|
||||
<div class="event-title">{{ getEventProperties(entity).label || 'Untitled' }}</div>
|
||||
<div class="event-title">{{ getEventProperties(occurrence.entity).label || 'Untitled' }}</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="event-title-compact">{{ getEventProperties(entity).label || 'Untitled' }}</div>
|
||||
<div class="event-title-compact">{{ getEventProperties(occurrence.entity).label || 'Untitled' }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,7 +104,6 @@ import { ref, computed, watch } from 'vue';
|
||||
import {
|
||||
startOfDay,
|
||||
getMultiDaySegments,
|
||||
getTimedEventsForDate,
|
||||
type MultiDaySegment,
|
||||
} from '@/utils/calendarHelpers';
|
||||
import { shiftLocalDays } from '@/utils/dateRanges';
|
||||
@@ -112,6 +111,8 @@ import { DAYS_VIEW_SPANS, spanToDays, type DaysViewSpan } from '@/types/spans';
|
||||
import type { EntityObject } from '@ChronoManager/models/entity';
|
||||
import type { CollectionObject } from '@ChronoManager/models/collection';
|
||||
import { EventObject } from '@ChronoManager/models/event';
|
||||
import { occurrencesForDay, occurrencesForRange } from '@/utils/occurrenceIndex';
|
||||
import type { CalendarOccurrence, CalendarOccurrenceIndex } from '@/types/occurrence';
|
||||
|
||||
const ALL_DAY_EVENT_HEIGHT = 24;
|
||||
|
||||
@@ -120,7 +121,7 @@ type CalendarCollection = CollectionObject;
|
||||
|
||||
const props = defineProps<{
|
||||
currentDate: Date;
|
||||
events: CalendarEntity[];
|
||||
occurrenceIndex: CalendarOccurrenceIndex;
|
||||
calendars: CalendarCollection[];
|
||||
initialSpan?: DaysViewSpan;
|
||||
}>();
|
||||
@@ -205,7 +206,12 @@ const allDaySegments = computed(() => {
|
||||
return visibleDates.value.findIndex(d => startOfDay(d).getTime() === targetDay.getTime());
|
||||
};
|
||||
|
||||
return getMultiDaySegments(props.events, rangeStart, rangeEnd, daysCount.value, getColumnIndex);
|
||||
const occurrences = occurrencesForRange(props.occurrenceIndex, {
|
||||
startMs: rangeStart.getTime(),
|
||||
endMs: shiftLocalDays(rangeEnd, 1).getTime(),
|
||||
});
|
||||
|
||||
return getMultiDaySegments(occurrences, rangeStart, rangeEnd, daysCount.value, getColumnIndex);
|
||||
});
|
||||
|
||||
function isToday(date: Date): boolean {
|
||||
@@ -213,8 +219,8 @@ function isToday(date: Date): boolean {
|
||||
return date.toDateString() === today.toDateString();
|
||||
}
|
||||
|
||||
function getTimedEvents(date: Date): CalendarEntity[] {
|
||||
return getTimedEventsForDate(props.events, date) as CalendarEntity[];
|
||||
function getTimedEvents(date: Date): CalendarOccurrence[] {
|
||||
return occurrencesForDay(props.occurrenceIndex, date).filter(occurrence => !occurrence.timeless);
|
||||
}
|
||||
|
||||
function getEventProperties(entity: CalendarEntity): EventObject {
|
||||
@@ -230,28 +236,22 @@ function getEventColor(entity: CalendarEntity): string {
|
||||
|
||||
function getAllDayEventStyle(segment: MultiDaySegment) {
|
||||
return {
|
||||
backgroundColor: getEventColor(segment.entity),
|
||||
backgroundColor: getEventColor(segment.occurrence.entity),
|
||||
left: `calc(${segment.startCol} / ${daysCount.value} * 100% + 4px)`,
|
||||
width: `calc(${segment.span} / ${daysCount.value} * 100% - 8px)`,
|
||||
top: `${segment.lane * ALL_DAY_EVENT_HEIGHT}px`,
|
||||
};
|
||||
}
|
||||
|
||||
function getEventStyle(entity: CalendarEntity) {
|
||||
const event = getEventProperties(entity);
|
||||
|
||||
if (!event.startsOn || !event.endsOn) {
|
||||
return { display: 'none' };
|
||||
}
|
||||
const startTime = new Date(event.startsOn);
|
||||
const endTime = new Date(event.endsOn);
|
||||
function getEventStyle(occurrence: CalendarOccurrence) {
|
||||
const startTime = new Date(occurrence.startMs);
|
||||
const start = startTime.getHours() * 60 + startTime.getMinutes();
|
||||
const duration = (endTime.getTime() - startTime.getTime()) / (1000 * 60);
|
||||
const duration = occurrence.durationMs / (1000 * 60);
|
||||
|
||||
return {
|
||||
top: `${(start / 1440) * 100}%`,
|
||||
height: `${(duration / 1440) * 100}%`,
|
||||
backgroundColor: getEventColor(entity),
|
||||
backgroundColor: getEventColor(occurrence.entity),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -265,8 +265,8 @@ function formatTime(date: Date): string {
|
||||
return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function formatEventDateTime(value: string | null): string {
|
||||
return value ? formatTime(new Date(value)) : '';
|
||||
function formatOccurrenceTime(timestamp: number): string {
|
||||
return formatTime(new Date(timestamp));
|
||||
}
|
||||
|
||||
function formatWeekDay(date: Date): string {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
||||
import {
|
||||
startOfDay,
|
||||
daysDiff,
|
||||
isMultiDay,
|
||||
getSingleDayEventsForDate,
|
||||
getMultiDaySegments,
|
||||
type MultiDaySegment,
|
||||
} from '@/utils/calendarHelpers';
|
||||
import { shiftLocalMonths } from '@/utils/dateRanges';
|
||||
import { occurrencesForDay, occurrencesForRange } from '@/utils/occurrenceIndex';
|
||||
import type { CalendarOccurrence, CalendarOccurrenceIndex } from '@/types/occurrence';
|
||||
|
||||
const EVENT_HEIGHT = 22; // Height of each event row in pixels
|
||||
const MAX_VISIBLE_EVENTS = 3; // Maximum event rows to show before "+N more"
|
||||
@@ -21,7 +21,7 @@ interface WeekData {
|
||||
|
||||
const props = defineProps<{
|
||||
currentDate: Date;
|
||||
events: any[];
|
||||
occurrenceIndex: CalendarOccurrenceIndex;
|
||||
calendars: any[];
|
||||
}>();
|
||||
|
||||
@@ -113,7 +113,21 @@ const weeks = computed<WeekData[]>(() => {
|
||||
}
|
||||
|
||||
// Get multiday segments for this week
|
||||
const { segments, laneCount } = getMultiDaySegments(weekStart, weekEnd);
|
||||
const occurrences = occurrencesForRange(props.occurrenceIndex, {
|
||||
startMs: weekStart.getTime(),
|
||||
endMs: new Date(
|
||||
weekEnd.getFullYear(),
|
||||
weekEnd.getMonth(),
|
||||
weekEnd.getDate() + 1,
|
||||
).getTime(),
|
||||
});
|
||||
const { segments, laneCount } = getMultiDaySegments(
|
||||
occurrences,
|
||||
weekStart,
|
||||
weekEnd,
|
||||
7,
|
||||
date => date.getDay(),
|
||||
);
|
||||
|
||||
weeksData.push({
|
||||
startDate: weekStart,
|
||||
@@ -126,77 +140,9 @@ const weeks = computed<WeekData[]>(() => {
|
||||
return weeksData;
|
||||
});
|
||||
|
||||
function getMultiDaySegments(weekStart: Date, weekEnd: Date): { segments: MultiDaySegment[]; laneCount: number } {
|
||||
const segments: MultiDaySegment[] = [];
|
||||
|
||||
// Filter multiday events that overlap this week
|
||||
const multiDayEvents = props.events.filter(entity => {
|
||||
if (!entity.properties?.startsOn || !isMultiDay(entity)) return false;
|
||||
const eventStart = startOfDay(new Date(entity.properties.startsOn));
|
||||
const eventEnd = startOfDay(new Date(entity.properties.endsOn));
|
||||
return eventStart <= weekEnd && eventEnd >= weekStart;
|
||||
});
|
||||
|
||||
// Sort: longer events first, then by start date
|
||||
multiDayEvents.sort((a, b) => {
|
||||
const aStart = new Date(a.properties.startsOn);
|
||||
const aEnd = new Date(a.properties.endsOn);
|
||||
const bStart = new Date(b.properties.startsOn);
|
||||
const bEnd = new Date(b.properties.endsOn);
|
||||
const aDuration = daysDiff(aStart, aEnd);
|
||||
const bDuration = daysDiff(bStart, bEnd);
|
||||
if (bDuration !== aDuration) return bDuration - aDuration;
|
||||
return aStart.getTime() - bStart.getTime();
|
||||
});
|
||||
|
||||
// Lane assignment
|
||||
const lanes: boolean[][] = []; // lanes[lane][dayOfWeek] = occupied
|
||||
|
||||
for (const entity of multiDayEvents) {
|
||||
const eventStart = startOfDay(new Date(entity.properties.startsOn));
|
||||
const eventEnd = startOfDay(new Date(entity.properties.endsOn));
|
||||
|
||||
// Clamp to this week
|
||||
const segStart = eventStart < weekStart ? weekStart : eventStart;
|
||||
const segEnd = eventEnd > weekEnd ? weekEnd : eventEnd;
|
||||
|
||||
const startCol = segStart.getDay();
|
||||
const endCol = segEnd.getDay();
|
||||
const span = endCol - startCol + 1;
|
||||
|
||||
// Find available lane
|
||||
let lane = 0;
|
||||
while (true) {
|
||||
if (!lanes[lane]) lanes[lane] = [false, false, false, false, false, false, false];
|
||||
|
||||
let isFree = true;
|
||||
for (let d = startCol; d <= endCol; d++) {
|
||||
if (lanes[lane][d]) { isFree = false; break; }
|
||||
}
|
||||
|
||||
if (isFree) {
|
||||
for (let d = startCol; d <= endCol; d++) lanes[lane][d] = true;
|
||||
break;
|
||||
}
|
||||
lane++;
|
||||
}
|
||||
|
||||
segments.push({
|
||||
entity,
|
||||
startCol,
|
||||
span,
|
||||
lane,
|
||||
isStart: eventStart >= weekStart,
|
||||
isEnd: eventEnd <= weekEnd,
|
||||
});
|
||||
}
|
||||
|
||||
return { segments, laneCount: lanes.length };
|
||||
}
|
||||
|
||||
// Single-day events for a specific date
|
||||
function getSingleDayEvents(date: Date): any[] {
|
||||
return getSingleDayEventsForDate(props.events, date);
|
||||
function getSingleDayEvents(date: Date): CalendarOccurrence[] {
|
||||
return occurrencesForDay(props.occurrenceIndex, date).filter(occurrence => !occurrence.multiDay);
|
||||
}
|
||||
|
||||
// Count hidden events (multiday beyond MAX + single day beyond remaining space)
|
||||
@@ -215,7 +161,7 @@ function isToday(date: Date): boolean {
|
||||
return date.toDateString() === today.toDateString();
|
||||
}
|
||||
|
||||
function getEventColor(entity: any): string {
|
||||
function getEventColor(entity: CalendarOccurrence['entity']): string {
|
||||
if (entity.properties?.color) return entity.properties.color;
|
||||
const calendar = props.calendars.find(cal => cal.identifier === entity.collection);
|
||||
return calendar?.properties?.color || '#1976D2';
|
||||
@@ -265,15 +211,15 @@ function getEventColor(entity: any): string {
|
||||
<!-- Single-day events -->
|
||||
<div class="cell-events">
|
||||
<div
|
||||
v-for="entity in getSingleDayEvents(cell.date).slice(0, MAX_VISIBLE_EVENTS - week.laneCount)"
|
||||
:key="entity.identifier"
|
||||
v-for="occurrence in getSingleDayEvents(cell.date).slice(0, MAX_VISIBLE_EVENTS - week.laneCount)"
|
||||
:key="occurrence.key"
|
||||
class="single-day-event"
|
||||
:style="{ backgroundColor: getEventColor(entity) }"
|
||||
@click.stop="$emit('event-click', entity)"
|
||||
@mouseenter="$emit('event-hover', { event: $event, entity })"
|
||||
:style="{ backgroundColor: getEventColor(occurrence.entity) }"
|
||||
@click.stop="$emit('event-click', occurrence.entity)"
|
||||
@mouseenter="$emit('event-hover', { event: $event, entity: occurrence.entity })"
|
||||
@mouseleave="$emit('event-hover-end')"
|
||||
>
|
||||
{{ entity.properties?.label || 'Untitled' }}
|
||||
{{ occurrence.entity.properties?.label || 'Untitled' }}
|
||||
</div>
|
||||
<div
|
||||
v-if="getHiddenCount(cell, week.laneCount) > 0"
|
||||
@@ -290,24 +236,24 @@ function getEventColor(entity: any): string {
|
||||
<div class="multiday-overlay">
|
||||
<div
|
||||
v-for="segment in week.multiDaySegments"
|
||||
:key="`${segment.entity.identifier}-${weekIndex}`"
|
||||
:key="`${segment.occurrence.key}-${weekIndex}`"
|
||||
class="multiday-event"
|
||||
:class="{
|
||||
'is-start': segment.isStart,
|
||||
'is-end': segment.isEnd,
|
||||
}"
|
||||
:style="{
|
||||
backgroundColor: getEventColor(segment.entity),
|
||||
backgroundColor: getEventColor(segment.occurrence.entity),
|
||||
left: `calc(${segment.startCol} / 7 * 100% + 4px)`,
|
||||
width: `calc(${segment.span} / 7 * 100% - 8px)`,
|
||||
top: `${34 + segment.lane * EVENT_HEIGHT}px`,
|
||||
}"
|
||||
@click.stop="$emit('event-click', segment.entity)"
|
||||
@mouseenter="$emit('event-hover', { event: $event, entity: segment.entity })"
|
||||
@click.stop="$emit('event-click', segment.occurrence.entity)"
|
||||
@mouseenter="$emit('event-hover', { event: $event, entity: segment.occurrence.entity })"
|
||||
@mouseleave="$emit('event-hover-end')"
|
||||
>
|
||||
<span v-if="segment.isStart" class="event-label">
|
||||
{{ segment.entity.properties?.label || 'Untitled' }}
|
||||
{{ segment.occurrence.entity.properties?.label || 'Untitled' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user