chore: standardize chrono provider
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -27,15 +27,15 @@
|
|||||||
<v-list-subheader>{{ formatAgendaDate(date) }}</v-list-subheader>
|
<v-list-subheader>{{ formatAgendaDate(date) }}</v-list-subheader>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="entity in dayEvents"
|
v-for="entity in dayEvents"
|
||||||
:key="entity.id"
|
:key="entity.identifier"
|
||||||
@click="$emit('event-click', entity)"
|
@click="$emit('event-click', entity)"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon :color="getEventColor(entity)">mdi-circle</v-icon>
|
<v-icon :color="getEventColor(entity)">mdi-circle</v-icon>
|
||||||
</template>
|
</template>
|
||||||
<v-list-item-title>{{ entity.data?.label || 'Untitled' }}</v-list-item-title>
|
<v-list-item-title>{{ entity.properties?.label || 'Untitled' }}</v-list-item-title>
|
||||||
<v-list-item-subtitle>
|
<v-list-item-subtitle>
|
||||||
{{ entity.data?.allDay ? 'All day' : `${entity.data?.startsOn ? formatTime(new Date(entity.data.startsOn)) : ''} - ${entity.data?.endsOn ? formatTime(new Date(entity.data.endsOn)) : ''}` }}
|
{{ entity.properties?.timeless ? 'All day' : `${entity.properties?.startsOn ? formatTime(new Date(entity.properties.startsOn)) : ''} - ${entity.properties?.endsOn ? formatTime(new Date(entity.properties.endsOn)) : ''}` }}
|
||||||
</v-list-item-subtitle>
|
</v-list-item-subtitle>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -132,17 +132,17 @@ const groupedEvents = computed(() => {
|
|||||||
const { start, end } = dateRange.value;
|
const { start, end } = dateRange.value;
|
||||||
|
|
||||||
const filtered = props.events.filter(e => {
|
const filtered = props.events.filter(e => {
|
||||||
if (!e.data?.startsOn) return false;
|
if (!e.properties?.startsOn) return false;
|
||||||
const eventStart = new Date(e.data.startsOn);
|
const eventStart = new Date(e.properties.startsOn);
|
||||||
return eventStart >= start && eventStart <= end;
|
return eventStart >= start && eventStart <= end;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sorted = filtered.sort((a, b) =>
|
const sorted = filtered.sort((a, b) =>
|
||||||
new Date(a.data.startsOn).getTime() - new Date(b.data.startsOn).getTime()
|
new Date(a.properties.startsOn).getTime() - new Date(b.properties.startsOn).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
sorted.forEach(entity => {
|
sorted.forEach(entity => {
|
||||||
const dateKey = new Date(entity.data.startsOn).toDateString();
|
const dateKey = new Date(entity.properties.startsOn).toDateString();
|
||||||
if (!grouped[dateKey]) {
|
if (!grouped[dateKey]) {
|
||||||
grouped[dateKey] = [];
|
grouped[dateKey] = [];
|
||||||
}
|
}
|
||||||
@@ -153,9 +153,9 @@ const groupedEvents = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function getEventColor(entity: any): string {
|
function getEventColor(entity: any): string {
|
||||||
if (entity.data?.color) return entity.data.color;
|
if (entity.properties?.color) return entity.properties.color;
|
||||||
const calendar = props.calendars.find(cal => cal.id === entity.in);
|
const calendar = props.calendars.find(cal => cal.identifier === entity.collection);
|
||||||
return calendar?.color || '#1976D2';
|
return calendar?.properties?.color || '#1976D2';
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTime(date: Date): string {
|
function formatTime(date: Date): string {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const dialogIcon = computed(() => {
|
|||||||
// Functions
|
// Functions
|
||||||
const onOpen = async () => {
|
const onOpen = async () => {
|
||||||
if (services.value.length === 0) {
|
if (services.value.length === 0) {
|
||||||
services.value = await servicesStore.list()
|
services.value = Object.values(await servicesStore.list())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.collection) {
|
if (!props.collection) {
|
||||||
@@ -71,10 +71,10 @@ const onOpen = async () => {
|
|||||||
// Clone the collection to avoid mutating the original
|
// Clone the collection to avoid mutating the original
|
||||||
editingCollection.value = props.collection.clone()
|
editingCollection.value = props.collection.clone()
|
||||||
|
|
||||||
if (props.collection.id !== null) {
|
if (props.mode === 'edit') {
|
||||||
// Edit mode - find the service
|
// Edit mode - find the service
|
||||||
editingCollectionService.value = services.value.find(s =>
|
editingCollectionService.value = services.value.find(s =>
|
||||||
s.provider === props.collection!.provider && s.id === props.collection!.service
|
s.provider === props.collection!.provider && s.identifier === props.collection!.service
|
||||||
) || null
|
) || null
|
||||||
} else {
|
} else {
|
||||||
// Create mode - use first service that can create
|
// Create mode - use first service that can create
|
||||||
@@ -102,7 +102,7 @@ const onColorSelect = (color: string | null, closeMenu = true) => {
|
|||||||
if (!editingCollection.value) {
|
if (!editingCollection.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
editingCollection.value.color = color
|
editingCollection.value.properties.color = color
|
||||||
if (closeMenu) {
|
if (closeMenu) {
|
||||||
colorMenuOpen.value = false
|
colorMenuOpen.value = false
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ watch(() => props.modelValue, async (newValue) => {
|
|||||||
label="Service"
|
label="Service"
|
||||||
:items="services.filter(s => s.capabilities?.CollectionCreate)"
|
:items="services.filter(s => s.capabilities?.CollectionCreate)"
|
||||||
item-title="label"
|
item-title="label"
|
||||||
item-value="id"
|
item-value="identifier"
|
||||||
required
|
required
|
||||||
:rules="[(v: ServiceObject) => !!v || 'Service is required']"
|
:rules="[(v: ServiceObject) => !!v || 'Service is required']"
|
||||||
/>
|
/>
|
||||||
@@ -152,7 +152,7 @@ watch(() => props.modelValue, async (newValue) => {
|
|||||||
<div v-if="mode === 'edit'" class="mb-4"><strong>Service:</strong> {{ editingCollection.service }}</div>
|
<div v-if="mode === 'edit'" class="mb-4"><strong>Service:</strong> {{ editingCollection.service }}</div>
|
||||||
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="editingCollection.label"
|
v-model="editingCollection.properties.label"
|
||||||
label="Label"
|
label="Label"
|
||||||
required
|
required
|
||||||
:rules="[(v: string) => !!v || 'Label is required']"
|
:rules="[(v: string) => !!v || 'Label is required']"
|
||||||
@@ -170,7 +170,7 @@ watch(() => props.modelValue, async (newValue) => {
|
|||||||
icon
|
icon
|
||||||
variant="text"
|
variant="text"
|
||||||
size="small"
|
size="small"
|
||||||
:style="{ color: editingCollection.color || 'var(--v-theme-on-surface)' }"
|
:style="{ color: editingCollection.properties.color || 'var(--v-theme-on-surface)' }"
|
||||||
aria-label="Select color"
|
aria-label="Select color"
|
||||||
title="Select color"
|
title="Select color"
|
||||||
>
|
>
|
||||||
@@ -203,11 +203,11 @@ watch(() => props.modelValue, async (newValue) => {
|
|||||||
variant="flat"
|
variant="flat"
|
||||||
size="small"
|
size="small"
|
||||||
class="color-menu-body__presets--swatch"
|
class="color-menu-body__presets--swatch"
|
||||||
:class="{ 'color-menu-body__presets--swatch--active': editingCollection.color === color }"
|
:class="{ 'color-menu-body__presets--swatch--active': editingCollection.properties.color === color }"
|
||||||
:style="{ backgroundColor: color }"
|
:style="{ backgroundColor: color }"
|
||||||
@click="onColorSelect(color)">
|
@click="onColorSelect(color)">
|
||||||
<v-icon
|
<v-icon
|
||||||
v-if="editingCollection.color === color"
|
v-if="editingCollection.properties.color === color"
|
||||||
icon="mdi-check"
|
icon="mdi-check"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
color="white"
|
color="white"
|
||||||
@@ -218,7 +218,7 @@ watch(() => props.modelValue, async (newValue) => {
|
|||||||
|
|
||||||
<div class="color-menu-body__picker">
|
<div class="color-menu-body__picker">
|
||||||
<v-color-picker
|
<v-color-picker
|
||||||
v-model="editingCollection.color"
|
v-model="editingCollection.properties.color"
|
||||||
mode="hex"
|
mode="hex"
|
||||||
hide-canvas
|
hide-canvas
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -232,7 +232,7 @@ watch(() => props.modelValue, async (newValue) => {
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
|
|
||||||
<v-textarea
|
<v-textarea
|
||||||
v-model="editingCollection.description"
|
v-model="editingCollection.properties.description"
|
||||||
label="Description"
|
label="Description"
|
||||||
rows="2"
|
rows="2"
|
||||||
/>
|
/>
|
||||||
@@ -240,7 +240,7 @@ watch(() => props.modelValue, async (newValue) => {
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col v-if="mode === 'edit'" cols="6">
|
<v-col v-if="mode === 'edit'" cols="6">
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="editingCollection.enabled"
|
v-model="editingCollection.properties.visibility"
|
||||||
label="Enabled"
|
label="Enabled"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ const filteredCollections = computed(() => {
|
|||||||
|
|
||||||
return collections.value.filter(collection => {
|
return collections.value.filter(collection => {
|
||||||
if (props.type === 'calendar') {
|
if (props.type === 'calendar') {
|
||||||
return collection.contents?.event
|
return collection.properties.contents?.event
|
||||||
} else if (props.type === 'tasklist') {
|
} else if (props.type === 'tasklist') {
|
||||||
return collection.contents?.task
|
return collection.properties.contents?.task
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -53,7 +53,7 @@ const displayIcon = computed(() => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
collections.value = await collectionsStore.list()
|
collections.value = Object.values(await collectionsStore.list())
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Chrono] - Failed to load collections:', error)
|
console.error('[Chrono] - Failed to load collections:', error)
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ const onCollectionEdit = (collection: CollectionObject) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onToggleVisibility = (collection: CollectionObject) => {
|
const onToggleVisibility = (collection: CollectionObject) => {
|
||||||
collection.enabled = !collection.enabled
|
collection.properties.visibility = collection.properties.visibility === false
|
||||||
emit('toggle-visibility', collection)
|
emit('toggle-visibility', collection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ defineExpose({
|
|||||||
async refresh() {
|
async refresh() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
collections.value = await collectionsStore.list()
|
collections.value = Object.values(await collectionsStore.list())
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Chrono] - Failed to load collections:', error)
|
console.error('[Chrono] - Failed to load collections:', error)
|
||||||
}
|
}
|
||||||
@@ -106,28 +106,28 @@ defineExpose({
|
|||||||
<v-list v-else density="compact" nav class="pa-0">
|
<v-list v-else density="compact" nav class="pa-0">
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="collection in filteredCollections"
|
v-for="collection in filteredCollections"
|
||||||
:key="collection.id"
|
:key="collection.identifier"
|
||||||
:value="collection.id"
|
:value="collection.identifier"
|
||||||
:active="selectedCollection?.id === collection.id"
|
:active="selectedCollection?.identifier === collection.identifier"
|
||||||
@click="onCollectionSelect(collection)"
|
@click="onCollectionSelect(collection)"
|
||||||
rounded="lg"
|
rounded="lg"
|
||||||
class="mb-1"
|
class="mb-1"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-checkbox-btn
|
<v-checkbox-btn
|
||||||
:model-value="collection.enabled !== false"
|
:model-value="collection.properties.visibility !== false"
|
||||||
:color="collection.color || 'primary'"
|
:color="collection.properties.color || 'primary'"
|
||||||
hide-details
|
hide-details
|
||||||
@click.stop="onToggleVisibility(collection)"
|
@click.stop="onToggleVisibility(collection)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item-title>{{ collection.label || 'Unnamed Collection' }}</v-list-item-title>
|
<v-list-item-title>{{ collection.properties.label || 'Unnamed Collection' }}</v-list-item-title>
|
||||||
|
|
||||||
<template #append>
|
<template #append>
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<v-icon
|
<v-icon
|
||||||
:color="collection.color || 'primary'"
|
:color="collection.properties.color || 'primary'"
|
||||||
size="small"
|
size="small"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
>mdi-circle</v-icon>
|
>mdi-circle</v-icon>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<div class="all-day-events-overlay">
|
<div class="all-day-events-overlay">
|
||||||
<div
|
<div
|
||||||
v-for="segment in allDaySegments.segments"
|
v-for="segment in allDaySegments.segments"
|
||||||
:key="segment.entity.id"
|
:key="segment.entity.identifier"
|
||||||
class="all-day-event"
|
class="all-day-event"
|
||||||
:class="{
|
:class="{
|
||||||
'is-start': segment.isStart,
|
'is-start': segment.isStart,
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
@mouseenter="emit('event-hover', { event: $event, entity: segment.entity })"
|
@mouseenter="emit('event-hover', { event: $event, entity: segment.entity })"
|
||||||
@mouseleave="emit('event-hover-end')"
|
@mouseleave="emit('event-hover-end')"
|
||||||
>
|
>
|
||||||
<span v-if="segment.isStart" class="event-label">{{ segment.entity.data?.label || 'Untitled' }}</span>
|
<span v-if="segment.isStart" class="event-label">{{ segment.entity.properties?.label || 'Untitled' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<div class="day-events" @click="handleDayClick($event, day)">
|
<div class="day-events" @click="handleDayClick($event, day)">
|
||||||
<div
|
<div
|
||||||
v-for="entity in getTimedEvents(day)"
|
v-for="entity in getTimedEvents(day)"
|
||||||
:key="entity.id"
|
:key="entity.identifier"
|
||||||
class="day-event"
|
class="day-event"
|
||||||
:style="getEventStyle(entity)"
|
:style="getEventStyle(entity)"
|
||||||
@click.stop="emit('event-click', entity)"
|
@click.stop="emit('event-click', entity)"
|
||||||
@@ -80,12 +80,12 @@
|
|||||||
>
|
>
|
||||||
<template v-if="daysCount <= 3">
|
<template v-if="daysCount <= 3">
|
||||||
<div class="event-time">
|
<div class="event-time">
|
||||||
{{ entity.data?.startsOn ? formatTime(new Date(entity.data.startsOn)) : '' }} - {{ entity.data?.endsOn ? formatTime(new Date(entity.data.endsOn)) : '' }}
|
{{ entity.properties?.startsOn ? formatTime(new Date(entity.properties.startsOn)) : '' }} - {{ entity.properties?.endsOn ? formatTime(new Date(entity.properties.endsOn)) : '' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="event-title">{{ entity.data?.label || 'Untitled' }}</div>
|
<div class="event-title">{{ entity.properties?.label || 'Untitled' }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="event-title-compact">{{ entity.data?.label || 'Untitled' }}</div>
|
<div class="event-title-compact">{{ entity.properties?.label || 'Untitled' }}</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -200,9 +200,9 @@ function getTimedEvents(date: Date): any[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEventColor(entity: any): string {
|
function getEventColor(entity: any): string {
|
||||||
if (entity.data?.color) return entity.data.color;
|
if (entity.properties?.color) return entity.properties.color;
|
||||||
const calendar = props.calendars.find(cal => cal.id === entity.in);
|
const calendar = props.calendars.find(cal => cal.identifier === entity.collection);
|
||||||
return calendar?.color || '#1976D2';
|
return calendar?.properties?.color || '#1976D2';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllDayEventStyle(segment: MultiDaySegment) {
|
function getAllDayEventStyle(segment: MultiDaySegment) {
|
||||||
@@ -215,11 +215,11 @@ function getAllDayEventStyle(segment: MultiDaySegment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEventStyle(entity: any) {
|
function getEventStyle(entity: any) {
|
||||||
if (!entity.data?.startsOn || !entity.data?.endsOn) {
|
if (!entity.properties?.startsOn || !entity.properties?.endsOn) {
|
||||||
return { display: 'none' };
|
return { display: 'none' };
|
||||||
}
|
}
|
||||||
const startTime = new Date(entity.data.startsOn);
|
const startTime = new Date(entity.properties.startsOn);
|
||||||
const endTime = new Date(entity.data.endsOn);
|
const endTime = new Date(entity.properties.endsOn);
|
||||||
const start = startTime.getHours() * 60 + startTime.getMinutes();
|
const start = startTime.getHours() * 60 + startTime.getMinutes();
|
||||||
const duration = (endTime.getTime() - startTime.getTime()) / (1000 * 60);
|
const duration = (endTime.getTime() - startTime.getTime()) / (1000 * 60);
|
||||||
|
|
||||||
|
|||||||
@@ -32,18 +32,18 @@ const emit = defineEmits<{
|
|||||||
// State
|
// State
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const selectedCollectionId = ref(props.entity?.in || props.collection?.id || props.calendars?.[0]?.id || '')
|
const selectedCollectionId = ref(props.entity?.collection || props.collection?.identifier || props.calendars?.[0]?.identifier || '')
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const mode = computed(() => props.mode)
|
const mode = computed(() => props.mode)
|
||||||
const entity = computed(() => props.entity || null)
|
const entity = computed(() => props.entity || null)
|
||||||
const entityObject = computed(() => entity.value?.data as EventObject ?? null)
|
const entityObject = computed(() => entity.value?.properties as EventObject ?? null)
|
||||||
const entityFresh = computed(() => entity.value?.id === null || entity.value?.id === undefined)
|
const entityFresh = computed(() => !entity.value?.identifier)
|
||||||
|
|
||||||
const calendarOptions = computed(() =>
|
const calendarOptions = computed(() =>
|
||||||
(props.calendars || []).map(cal => ({
|
(props.calendars || []).map(cal => ({
|
||||||
title: cal.label || 'Unnamed Calendar',
|
title: cal.properties.label || 'Unnamed Calendar',
|
||||||
value: cal.id,
|
value: cal.identifier,
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ const cancelEdit = () => {
|
|||||||
|
|
||||||
const saveEntity = async () => {
|
const saveEntity = async () => {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
const targetCollection = (props.calendars || []).find(cal => cal.id === selectedCollectionId.value)
|
const targetCollection = (props.calendars || []).find(cal => cal.identifier === selectedCollectionId.value)
|
||||||
emit('save', entity.value!, targetCollection || props.collection)
|
emit('save', entity.value!, targetCollection || props.collection)
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ const removeNotification = (key: string) => {
|
|||||||
|
|
||||||
<div v-else class="pa-4">
|
<div v-else class="pa-4">
|
||||||
<v-select
|
<v-select
|
||||||
v-if="mode === 'edit' && !entity.id"
|
v-if="mode === 'edit' && !entity.identifier"
|
||||||
v-model="selectedCollectionId"
|
v-model="selectedCollectionId"
|
||||||
:items="calendarOptions"
|
:items="calendarOptions"
|
||||||
label="Calendar"
|
label="Calendar"
|
||||||
|
|||||||
@@ -41,25 +41,25 @@ const popupStyle = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const hasLocation = computed(() => {
|
const hasLocation = computed(() => {
|
||||||
return props.event?.data?.locationsPhysical && Object.keys(props.event.data.locationsPhysical).length > 0 ||
|
return props.event?.properties?.locationsPhysical && Object.keys(props.event.properties.locationsPhysical).length > 0 ||
|
||||||
props.event?.data?.locationsVirtual && Object.keys(props.event.data.locationsVirtual).length > 0
|
props.event?.properties?.locationsVirtual && Object.keys(props.event.properties.locationsVirtual).length > 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const firstLocation = computed(() => {
|
const firstLocation = computed(() => {
|
||||||
if (props.event?.data?.locationsPhysical && Object.keys(props.event.data.locationsPhysical).length > 0) {
|
if (props.event?.properties?.locationsPhysical && Object.keys(props.event.properties.locationsPhysical).length > 0) {
|
||||||
const key = Object.keys(props.event.data.locationsPhysical)[0]
|
const key = Object.keys(props.event.properties.locationsPhysical)[0]
|
||||||
return props.event.data.locationsPhysical[key]
|
return props.event.properties.locationsPhysical[key]
|
||||||
}
|
}
|
||||||
if (props.event?.data?.locationsVirtual && Object.keys(props.event.data.locationsVirtual).length > 0) {
|
if (props.event?.properties?.locationsVirtual && Object.keys(props.event.properties.locationsVirtual).length > 0) {
|
||||||
const key = Object.keys(props.event.data.locationsVirtual)[0]
|
const key = Object.keys(props.event.properties.locationsVirtual)[0]
|
||||||
return props.event.data.locationsVirtual[key]
|
return props.event.properties.locationsVirtual[key]
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
const participantCount = computed(() => {
|
const participantCount = computed(() => {
|
||||||
if (!props.event?.data?.participants) return 0
|
if (!props.event?.properties?.participants) return 0
|
||||||
return Object.keys(props.event.data.participants).length
|
return Object.keys(props.event.properties.participants).length
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -75,18 +75,18 @@ const participantCount = computed(() => {
|
|||||||
<v-card-text class="pa-3">
|
<v-card-text class="pa-3">
|
||||||
<div class="d-flex align-center mb-2">
|
<div class="d-flex align-center mb-2">
|
||||||
<v-icon icon="mdi-calendar" size="small" class="mr-2" />
|
<v-icon icon="mdi-calendar" size="small" class="mr-2" />
|
||||||
<div class="text-subtitle-1 font-weight-bold">{{ event.data?.label || 'Untitled Event' }}</div>
|
<div class="text-subtitle-1 font-weight-bold">{{ event.properties?.label || 'Untitled Event' }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="event-popup-details">
|
<div class="event-popup-details">
|
||||||
<div class="d-flex align-center mb-1">
|
<div class="d-flex align-center mb-1">
|
||||||
<v-icon icon="mdi-clock-outline" size="small" class="mr-2 text-grey" />
|
<v-icon icon="mdi-clock-outline" size="small" class="mr-2 text-grey" />
|
||||||
<div class="text-caption">
|
<div class="text-caption">
|
||||||
<div v-if="event.data?.timeless">
|
<div v-if="event.properties?.timeless">
|
||||||
All Day - {{ new Date(event.data.startsOn).toLocaleDateString() }}
|
All Day - {{ new Date(event.properties.startsOn).toLocaleDateString() }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ formatTime(event.data?.startsOn) }} - {{ formatTime(event.data?.endsOn) }}
|
{{ formatTime(event.properties?.startsOn) }} - {{ formatTime(event.properties?.endsOn) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,10 +98,10 @@ const participantCount = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="event.data?.organizer" class="d-flex align-center mb-1">
|
<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" />
|
<v-icon icon="mdi-account" size="small" class="mr-2 text-grey" />
|
||||||
<div class="text-caption">
|
<div class="text-caption">
|
||||||
{{ event.data.organizer.name || event.data.organizer.address }}
|
{{ event.properties.organizer.name || event.properties.organizer.address }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -112,13 +112,13 @@ const participantCount = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="event.data?.description" class="mt-2 pt-2 border-t">
|
<div v-if="event.properties?.description" class="mt-2 pt-2 border-t">
|
||||||
<div class="text-caption text-grey">{{ event.data.description }}</div>
|
<div class="text-caption text-grey">{{ event.properties.description }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="event.data?.tags && event.data.tags.length > 0" class="mt-2">
|
<div v-if="event.properties?.tags && event.properties.tags.length > 0" class="mt-2">
|
||||||
<v-chip
|
<v-chip
|
||||||
v-for="(tag, index) in event.data.tags.slice(0, 3)"
|
v-for="(tag, index) in event.properties.tags.slice(0, 3)"
|
||||||
:key="index"
|
:key="index"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
@@ -126,8 +126,8 @@ const participantCount = computed(() => {
|
|||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
<span v-if="event.data.tags.length > 3" class="text-caption text-grey">
|
<span v-if="event.properties.tags.length > 3" class="text-caption text-grey">
|
||||||
+{{ event.data.tags.length - 3 }} more
|
+{{ event.properties.tags.length - 3 }} more
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -141,18 +141,18 @@ function getMultiDaySegments(weekStart: Date, weekEnd: Date): { segments: MultiD
|
|||||||
|
|
||||||
// Filter multiday events that overlap this week
|
// Filter multiday events that overlap this week
|
||||||
const multiDayEvents = props.events.filter(entity => {
|
const multiDayEvents = props.events.filter(entity => {
|
||||||
if (!entity.data?.startsOn || !isMultiDay(entity)) return false;
|
if (!entity.properties?.startsOn || !isMultiDay(entity)) return false;
|
||||||
const eventStart = startOfDay(new Date(entity.data.startsOn));
|
const eventStart = startOfDay(new Date(entity.properties.startsOn));
|
||||||
const eventEnd = startOfDay(new Date(entity.data.endsOn));
|
const eventEnd = startOfDay(new Date(entity.properties.endsOn));
|
||||||
return eventStart <= weekEnd && eventEnd >= weekStart;
|
return eventStart <= weekEnd && eventEnd >= weekStart;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort: longer events first, then by start date
|
// Sort: longer events first, then by start date
|
||||||
multiDayEvents.sort((a, b) => {
|
multiDayEvents.sort((a, b) => {
|
||||||
const aStart = new Date(a.data.startsOn);
|
const aStart = new Date(a.properties.startsOn);
|
||||||
const aEnd = new Date(a.data.endsOn);
|
const aEnd = new Date(a.properties.endsOn);
|
||||||
const bStart = new Date(b.data.startsOn);
|
const bStart = new Date(b.properties.startsOn);
|
||||||
const bEnd = new Date(b.data.endsOn);
|
const bEnd = new Date(b.properties.endsOn);
|
||||||
const aDuration = daysDiff(aStart, aEnd);
|
const aDuration = daysDiff(aStart, aEnd);
|
||||||
const bDuration = daysDiff(bStart, bEnd);
|
const bDuration = daysDiff(bStart, bEnd);
|
||||||
if (bDuration !== aDuration) return bDuration - aDuration;
|
if (bDuration !== aDuration) return bDuration - aDuration;
|
||||||
@@ -163,8 +163,8 @@ function getMultiDaySegments(weekStart: Date, weekEnd: Date): { segments: MultiD
|
|||||||
const lanes: boolean[][] = []; // lanes[lane][dayOfWeek] = occupied
|
const lanes: boolean[][] = []; // lanes[lane][dayOfWeek] = occupied
|
||||||
|
|
||||||
for (const entity of multiDayEvents) {
|
for (const entity of multiDayEvents) {
|
||||||
const eventStart = startOfDay(new Date(entity.data.startsOn));
|
const eventStart = startOfDay(new Date(entity.properties.startsOn));
|
||||||
const eventEnd = startOfDay(new Date(entity.data.endsOn));
|
const eventEnd = startOfDay(new Date(entity.properties.endsOn));
|
||||||
|
|
||||||
// Clamp to this week
|
// Clamp to this week
|
||||||
const segStart = eventStart < weekStart ? weekStart : eventStart;
|
const segStart = eventStart < weekStart ? weekStart : eventStart;
|
||||||
@@ -226,9 +226,9 @@ function isToday(date: Date): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEventColor(entity: any): string {
|
function getEventColor(entity: any): string {
|
||||||
if (entity.data?.color) return entity.data.color;
|
if (entity.properties?.color) return entity.properties.color;
|
||||||
const calendar = props.calendars.find(cal => cal.id === entity.in);
|
const calendar = props.calendars.find(cal => cal.identifier === entity.collection);
|
||||||
return calendar?.color || '#1976D2';
|
return calendar?.properties?.color || '#1976D2';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -276,14 +276,14 @@ function getEventColor(entity: any): string {
|
|||||||
<div class="cell-events">
|
<div class="cell-events">
|
||||||
<div
|
<div
|
||||||
v-for="entity in getSingleDayEvents(cell.date).slice(0, MAX_VISIBLE_EVENTS - week.laneCount)"
|
v-for="entity in getSingleDayEvents(cell.date).slice(0, MAX_VISIBLE_EVENTS - week.laneCount)"
|
||||||
:key="entity.id"
|
:key="entity.identifier"
|
||||||
class="single-day-event"
|
class="single-day-event"
|
||||||
:style="{ backgroundColor: getEventColor(entity) }"
|
:style="{ backgroundColor: getEventColor(entity) }"
|
||||||
@click.stop="$emit('event-click', entity)"
|
@click.stop="$emit('event-click', entity)"
|
||||||
@mouseenter="$emit('event-hover', { event: $event, entity })"
|
@mouseenter="$emit('event-hover', { event: $event, entity })"
|
||||||
@mouseleave="$emit('event-hover-end')"
|
@mouseleave="$emit('event-hover-end')"
|
||||||
>
|
>
|
||||||
{{ entity.data?.label || 'Untitled' }}
|
{{ entity.properties?.label || 'Untitled' }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="getHiddenCount(cell, week.laneCount) > 0"
|
v-if="getHiddenCount(cell, week.laneCount) > 0"
|
||||||
@@ -300,7 +300,7 @@ function getEventColor(entity: any): string {
|
|||||||
<div class="multiday-overlay">
|
<div class="multiday-overlay">
|
||||||
<div
|
<div
|
||||||
v-for="segment in week.multiDaySegments"
|
v-for="segment in week.multiDaySegments"
|
||||||
:key="`${segment.entity.id}-${weekIndex}`"
|
:key="`${segment.entity.identifier}-${weekIndex}`"
|
||||||
class="multiday-event"
|
class="multiday-event"
|
||||||
:class="{
|
:class="{
|
||||||
'is-start': segment.isStart,
|
'is-start': segment.isStart,
|
||||||
@@ -317,7 +317,7 @@ function getEventColor(entity: any): string {
|
|||||||
@mouseleave="$emit('event-hover-end')"
|
@mouseleave="$emit('event-hover-end')"
|
||||||
>
|
>
|
||||||
<span v-if="segment.isStart" class="event-label">
|
<span v-if="segment.isStart" class="event-label">
|
||||||
{{ segment.entity.data?.label || 'Untitled' }}
|
{{ segment.entity.properties?.label || 'Untitled' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card v-if="entity" class="task-editor">
|
<v-card v-if="entity" class="task-editor">
|
||||||
<v-card-title class="d-flex justify-space-between align-center">
|
<v-card-title class="d-flex justify-space-between align-center">
|
||||||
<span>{{ entity.id ? entity.data?.label || 'Untitled Task' : 'New Task' }}</span>
|
<span>{{ entity.identifier ? entity.properties?.label || 'Untitled Task' : 'New Task' }}</span>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="mode === 'view' && canEdit"
|
v-if="mode === 'view' && canEdit"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<v-card-text class="task-editor-content">
|
<v-card-text class="task-editor-content">
|
||||||
<v-form ref="formRef">
|
<v-form ref="formRef">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="entity.data.label"
|
v-model="entity.properties.label"
|
||||||
label="Title"
|
label="Title"
|
||||||
:rules="[v => !!v || 'Title is required']"
|
:rules="[v => !!v || 'Title is required']"
|
||||||
:readonly="!isEditing"
|
:readonly="!isEditing"
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
></v-select>
|
></v-select>
|
||||||
|
|
||||||
<v-textarea
|
<v-textarea
|
||||||
v-model="entity.data.description"
|
v-model="entity.properties.description"
|
||||||
label="Description"
|
label="Description"
|
||||||
:readonly="!isEditing"
|
:readonly="!isEditing"
|
||||||
:variant="isEditing ? 'outlined' : 'plain'"
|
:variant="isEditing ? 'outlined' : 'plain'"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
></v-textarea>
|
></v-textarea>
|
||||||
|
|
||||||
<v-select
|
<v-select
|
||||||
v-model="entity.data.priority"
|
v-model="entity.properties.priority"
|
||||||
:items="priorityOptions"
|
:items="priorityOptions"
|
||||||
label="Priority"
|
label="Priority"
|
||||||
:readonly="!isEditing"
|
:readonly="!isEditing"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
|
||||||
<v-combobox
|
<v-combobox
|
||||||
v-model="entity.data.categories"
|
v-model="entity.properties.categories"
|
||||||
label="Tags"
|
label="Tags"
|
||||||
multiple
|
multiple
|
||||||
chips
|
chips
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
|
|
||||||
<div class="d-flex gap-4">
|
<div class="d-flex gap-4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model.number="entity.data.estimatedTime"
|
v-model.number="entity.properties.estimatedTime"
|
||||||
label="Estimated Time (minutes)"
|
label="Estimated Time (minutes)"
|
||||||
type="number"
|
type="number"
|
||||||
:readonly="!isEditing"
|
:readonly="!isEditing"
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
class="flex-1-1"
|
class="flex-1-1"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model.number="entity.data.actualTime"
|
v-model.number="entity.properties.actualTime"
|
||||||
label="Actual Time (minutes)"
|
label="Actual Time (minutes)"
|
||||||
type="number"
|
type="number"
|
||||||
:readonly="!isEditing"
|
:readonly="!isEditing"
|
||||||
@@ -108,13 +108,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Metadata for view mode -->
|
<!-- Metadata for view mode -->
|
||||||
<div v-if="mode === 'view' && entity.data.created" class="mt-4">
|
<div v-if="mode === 'view' && entity.properties.created" class="mt-4">
|
||||||
<v-divider class="mb-4"></v-divider>
|
<v-divider class="mb-4"></v-divider>
|
||||||
<div class="text-caption text-medium-emphasis">
|
<div class="text-caption text-medium-emphasis">
|
||||||
<div>Created: {{ new Date(entity.data.created).toLocaleString() }}</div>
|
<div>Created: {{ new Date(entity.properties.created).toLocaleString() }}</div>
|
||||||
<div v-if="entity.data.modified">Modified: {{ new Date(entity.data.modified).toLocaleString() }}</div>
|
<div v-if="entity.properties.modified">Modified: {{ new Date(entity.properties.modified).toLocaleString() }}</div>
|
||||||
<div v-if="entity.data.status === 'completed' && entity.data.completedOn">
|
<div v-if="entity.properties.status === 'completed' && entity.properties.completedOn">
|
||||||
Completed: {{ new Date(entity.data.completedOn).toLocaleString() }}
|
Completed: {{ new Date(entity.properties.completedOn).toLocaleString() }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isExternalTask" class="mt-2">
|
<div v-if="isExternalTask" class="mt-2">
|
||||||
<v-chip size="small" color="info" variant="tonal">
|
<v-chip size="small" color="info" variant="tonal">
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="mode === 'edit' && entity.id && canDelete"
|
v-if="mode === 'edit' && entity.identifier && canDelete"
|
||||||
color="error"
|
color="error"
|
||||||
variant="text"
|
variant="text"
|
||||||
@click="handleDelete"
|
@click="handleDelete"
|
||||||
@@ -182,17 +182,17 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const selectedCollectionId = ref(props.entity?.in || props.collection?.id || props.lists[0]?.id || '');
|
const selectedCollectionId = ref(props.entity?.collection || props.collection?.identifier || props.lists[0]?.identifier || '');
|
||||||
|
|
||||||
// Determine permissions based on entity metadata
|
// Determine permissions based on entity metadata
|
||||||
const isExternalTask = computed(() => {
|
const isExternalTask = computed(() => {
|
||||||
// Check if task was assigned by another user
|
// Check if task was assigned by another user
|
||||||
return props.entity?.data?.external === true || props.entity?.data?.assignedBy;
|
return props.entity?.properties?.external === true || props.entity?.properties?.assignedBy;
|
||||||
});
|
});
|
||||||
|
|
||||||
const canEdit = computed(() => {
|
const canEdit = computed(() => {
|
||||||
if (!props.entity) return false;
|
if (!props.entity) return false;
|
||||||
if (props.entity.data?.readonly === true) return false;
|
if (props.entity.properties?.readonly === true) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,12 +203,12 @@ const canEditDates = computed(() => {
|
|||||||
|
|
||||||
const canChangeCollection = computed(() => {
|
const canChangeCollection = computed(() => {
|
||||||
// Can't move external tasks to different lists
|
// Can't move external tasks to different lists
|
||||||
return !isExternalTask.value && !props.entity?.id;
|
return !isExternalTask.value && !props.entity?.identifier;
|
||||||
});
|
});
|
||||||
|
|
||||||
const canDelete = computed(() => {
|
const canDelete = computed(() => {
|
||||||
// Can't delete external/readonly tasks
|
// Can't delete external/readonly tasks
|
||||||
return !isExternalTask.value && props.entity?.data?.readonly !== true;
|
return !isExternalTask.value && props.entity?.properties?.readonly !== true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEditing = computed(() => props.mode === 'edit');
|
const isEditing = computed(() => props.mode === 'edit');
|
||||||
@@ -218,25 +218,25 @@ const startDate = ref('');
|
|||||||
|
|
||||||
// Watch for entity changes to update date fields
|
// Watch for entity changes to update date fields
|
||||||
watch(() => props.entity, (newEntity) => {
|
watch(() => props.entity, (newEntity) => {
|
||||||
if (newEntity?.data?.dueOn) {
|
if (newEntity?.properties?.dueOn) {
|
||||||
dueDate.value = formatDate(new Date(newEntity.data.dueOn));
|
dueDate.value = formatDate(new Date(newEntity.properties.dueOn));
|
||||||
} else {
|
} else {
|
||||||
dueDate.value = '';
|
dueDate.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newEntity?.data?.startsOn) {
|
if (newEntity?.properties?.startsOn) {
|
||||||
startDate.value = formatDate(new Date(newEntity.data.startsOn));
|
startDate.value = formatDate(new Date(newEntity.properties.startsOn));
|
||||||
} else {
|
} else {
|
||||||
startDate.value = '';
|
startDate.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCollectionId.value = newEntity?.in || props.collection?.id || props.lists[0]?.id || '';
|
selectedCollectionId.value = newEntity?.collection || props.collection?.identifier || props.lists[0]?.identifier || '';
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const listOptions = computed(() =>
|
const listOptions = computed(() =>
|
||||||
props.lists.map(list => ({
|
props.lists.map(list => ({
|
||||||
title: list.label || 'Unnamed List',
|
title: list.properties.label || 'Unnamed List',
|
||||||
value: list.id,
|
value: list.identifier,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -248,13 +248,13 @@ const priorityOptions = [
|
|||||||
|
|
||||||
watch(dueDate, (newVal) => {
|
watch(dueDate, (newVal) => {
|
||||||
if (props.entity && isEditing.value) {
|
if (props.entity && isEditing.value) {
|
||||||
props.entity.data.dueOn = newVal ? new Date(newVal).toISOString() : null;
|
props.entity.properties.dueOn = newVal ? new Date(newVal).toISOString() : null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(startDate, (newVal) => {
|
watch(startDate, (newVal) => {
|
||||||
if (props.entity && isEditing.value) {
|
if (props.entity && isEditing.value) {
|
||||||
props.entity.data.startsOn = newVal ? new Date(newVal).toISOString() : null;
|
props.entity.properties.startsOn = newVal ? new Date(newVal).toISOString() : null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ async function handleSave() {
|
|||||||
const { valid } = await formRef.value.validate();
|
const { valid } = await formRef.value.validate();
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
|
|
||||||
const targetCollection = props.lists.find(list => list.id === selectedCollectionId.value);
|
const targetCollection = props.lists.find(list => list.identifier === selectedCollectionId.value);
|
||||||
emit('save', props.entity, targetCollection || props.collection);
|
emit('save', props.entity, targetCollection || props.collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ function handleCancel() {
|
|||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (props.entity) {
|
if (props.entity) {
|
||||||
const targetCollection = props.lists.find(list => list.id === selectedCollectionId.value);
|
const targetCollection = props.lists.find(list => list.identifier === selectedCollectionId.value);
|
||||||
emit('delete', props.entity, targetCollection || props.collection);
|
emit('delete', props.entity, targetCollection || props.collection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,41 +21,41 @@
|
|||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="entity in filteredTasks"
|
v-for="entity in filteredTasks"
|
||||||
:key="entity.id"
|
:key="entity.identifier"
|
||||||
class="task-item"
|
class="task-item"
|
||||||
:class="{ 'completed': entity.data?.status === 'completed' }"
|
:class="{ 'completed': entity.properties?.status === 'completed' }"
|
||||||
@click="$emit('task-click', entity)"
|
@click="$emit('task-click', entity)"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-checkbox-btn
|
<v-checkbox-btn
|
||||||
:model-value="entity.data?.status === 'completed'"
|
:model-value="entity.properties?.status === 'completed'"
|
||||||
hide-details
|
hide-details
|
||||||
@click.stop="$emit('toggle-complete', entity.id)"
|
@click.stop="$emit('toggle-complete', entity.identifier)"
|
||||||
></v-checkbox-btn>
|
></v-checkbox-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="task-content">
|
<div class="task-content">
|
||||||
<div class="task-title" :class="{ 'completed': entity.data?.status === 'completed' }">
|
<div class="task-title" :class="{ 'completed': entity.properties?.status === 'completed' }">
|
||||||
{{ entity.data?.label || 'Untitled Task' }}
|
{{ entity.properties?.label || 'Untitled Task' }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="entity.data?.description" class="task-description text-caption">
|
<div v-if="entity.properties?.description" class="task-description text-caption">
|
||||||
{{ entity.data.description }}
|
{{ entity.properties.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="task-meta">
|
<div class="task-meta">
|
||||||
<span v-if="entity.data?.dueOn" class="due-date">
|
<span v-if="entity.properties?.dueOn" class="due-date">
|
||||||
<v-icon size="small">mdi-calendar</v-icon>
|
<v-icon size="small">mdi-calendar</v-icon>
|
||||||
{{ formatDueDate(new Date(entity.data.dueOn)) }}
|
{{ formatDueDate(new Date(entity.properties.dueOn)) }}
|
||||||
</span>
|
</span>
|
||||||
<v-chip
|
<v-chip
|
||||||
v-if="entity.data?.priority"
|
v-if="entity.properties?.priority"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
:class="`priority-${entity.data.priority === 1 ? 'high' : entity.data.priority === 2 ? 'medium' : 'low'}`"
|
:class="`priority-${entity.properties.priority === 1 ? 'high' : entity.properties.priority === 2 ? 'medium' : 'low'}`"
|
||||||
class="priority-badge"
|
class="priority-badge"
|
||||||
>
|
>
|
||||||
{{ entity.data.priority === 1 ? 'high' : entity.data.priority === 2 ? 'medium' : 'low' }}
|
{{ entity.properties.priority === 1 ? 'high' : entity.properties.priority === 2 ? 'medium' : 'low' }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
<v-chip
|
<v-chip
|
||||||
v-for="tag in (entity.data?.categories || [])"
|
v-for="tag in (entity.properties?.categories || [])"
|
||||||
:key="tag"
|
:key="tag"
|
||||||
size="x-small"
|
size="x-small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -100,34 +100,34 @@ const filteredTasks = computed(() => {
|
|||||||
|
|
||||||
// Filter by tab
|
// Filter by tab
|
||||||
if (activeTab.value === 'active') {
|
if (activeTab.value === 'active') {
|
||||||
filtered = filtered.filter(entity => entity.data?.status !== 'completed');
|
filtered = filtered.filter(entity => entity.properties?.status !== 'completed');
|
||||||
} else if (activeTab.value === 'completed') {
|
} else if (activeTab.value === 'completed') {
|
||||||
filtered = filtered.filter(entity => entity.data?.status === 'completed');
|
filtered = filtered.filter(entity => entity.properties?.status === 'completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by priority
|
// Filter by priority
|
||||||
if (filterPriority.value) {
|
if (filterPriority.value) {
|
||||||
const priorityMap = { high: 1, medium: 2, low: 3 };
|
const priorityMap = { high: 1, medium: 2, low: 3 };
|
||||||
const targetPriority = priorityMap[filterPriority.value];
|
const targetPriority = priorityMap[filterPriority.value];
|
||||||
filtered = filtered.filter(entity => entity.data?.priority === targetPriority);
|
filtered = filtered.filter(entity => entity.properties?.priority === targetPriority);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort: incomplete first, then by due date, then by priority
|
// Sort: incomplete first, then by due date, then by priority
|
||||||
return filtered.sort((a, b) => {
|
return filtered.sort((a, b) => {
|
||||||
const aCompleted = a.data?.status === 'completed';
|
const aCompleted = a.properties?.status === 'completed';
|
||||||
const bCompleted = b.data?.status === 'completed';
|
const bCompleted = b.properties?.status === 'completed';
|
||||||
if (aCompleted !== bCompleted) {
|
if (aCompleted !== bCompleted) {
|
||||||
return aCompleted ? 1 : -1;
|
return aCompleted ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.data?.dueOn && b.data?.dueOn) {
|
if (a.properties?.dueOn && b.properties?.dueOn) {
|
||||||
return new Date(a.data.dueOn).getTime() - new Date(b.data.dueOn).getTime();
|
return new Date(a.properties.dueOn).getTime() - new Date(b.properties.dueOn).getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.data?.dueOn) return -1;
|
if (a.properties?.dueOn) return -1;
|
||||||
if (b.data?.dueOn) return 1;
|
if (b.properties?.dueOn) return 1;
|
||||||
|
|
||||||
return (a.data?.priority || 4) - (b.data?.priority || 4);
|
return (a.properties?.priority || 4) - (b.properties?.priority || 4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { useEntitiesStore } from '@ChronoManager/stores/entitiesStore';
|
|||||||
import { useServicesStore } from '@ChronoManager/stores/servicesStore';
|
import { useServicesStore } from '@ChronoManager/stores/servicesStore';
|
||||||
import { CollectionObject } from '@ChronoManager/models/collection';
|
import { CollectionObject } from '@ChronoManager/models/collection';
|
||||||
import { EntityObject } from '@ChronoManager/models/entity';
|
import { EntityObject } from '@ChronoManager/models/entity';
|
||||||
|
import { EventObject } from '@ChronoManager/models/event';
|
||||||
|
import { TaskObject } from '@ChronoManager/models/task';
|
||||||
import { ServiceObject } from '@ChronoManager/models/service';
|
import { ServiceObject } from '@ChronoManager/models/service';
|
||||||
import type { CalendarView as CalendarViewType } from '@/types';
|
import type { CalendarView as CalendarViewType } from '@/types';
|
||||||
import CollectionList from '@/components/CollectionList.vue';
|
import CollectionList from '@/components/CollectionList.vue';
|
||||||
@@ -45,19 +47,19 @@ const selectedCollection = ref<CollectionObject | null>(null);
|
|||||||
|
|
||||||
// Computed - filter collections and entities
|
// Computed - filter collections and entities
|
||||||
const calendars = computed(() => {
|
const calendars = computed(() => {
|
||||||
return collections.value.filter(col => col.contents?.event);
|
return collections.value.filter(col => col.properties.contents?.event);
|
||||||
});
|
});
|
||||||
|
|
||||||
const taskLists = computed(() => {
|
const taskLists = computed(() => {
|
||||||
return collections.value.filter(col => col.contents?.task);
|
return collections.value.filter(col => col.properties.contents?.task);
|
||||||
});
|
});
|
||||||
|
|
||||||
const events = computed(() => {
|
const events = computed(() => {
|
||||||
return entities.value.filter(entity => entity.data && (entity.data as any).type === 'event');
|
return entities.value.filter(entity => entity.properties && (entity.properties as any).type === 'event');
|
||||||
});
|
});
|
||||||
|
|
||||||
const tasks = computed(() => {
|
const tasks = computed(() => {
|
||||||
return entities.value.filter(entity => entity.data && (entity.data as any).type === 'task');
|
return entities.value.filter(entity => entity.properties && (entity.properties as any).type === 'task');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dialog state
|
// Dialog state
|
||||||
@@ -75,11 +77,11 @@ const isTaskView = computed(() => viewMode.value === 'tasks');
|
|||||||
|
|
||||||
const filteredEvents = computed(() => {
|
const filteredEvents = computed(() => {
|
||||||
const visibleCalendarIds = calendars.value
|
const visibleCalendarIds = calendars.value
|
||||||
.filter(cal => cal.enabled !== false)
|
.filter(cal => cal.properties.visibility !== false)
|
||||||
.map(cal => cal.id);
|
.map(cal => cal.identifier);
|
||||||
|
|
||||||
return events.value.filter(event =>
|
return events.value.filter(event =>
|
||||||
visibleCalendarIds.includes(event.in)
|
visibleCalendarIds.includes(event.collection)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,8 +96,8 @@ function selectCalendar(calendar: CollectionObject) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createCalendar() {
|
function createCalendar() {
|
||||||
editingCollection.value = collectionsStore.fresh();
|
editingCollection.value = new CollectionObject();
|
||||||
editingCollection.value.contents = { event: true };
|
editingCollection.value.properties.contents = { event: true };
|
||||||
collectionEditorMode.value = 'create';
|
collectionEditorMode.value = 'create';
|
||||||
collectionEditorType.value = 'calendar';
|
collectionEditorType.value = 'calendar';
|
||||||
showCollectionEditor.value = true;
|
showCollectionEditor.value = true;
|
||||||
@@ -110,7 +112,12 @@ function editCalendar(collection: CollectionObject) {
|
|||||||
|
|
||||||
async function toggleCalendarVisibility(collection: CollectionObject) {
|
async function toggleCalendarVisibility(collection: CollectionObject) {
|
||||||
try {
|
try {
|
||||||
await collectionsStore.modify(collection);
|
await collectionsStore.update(
|
||||||
|
collection.provider,
|
||||||
|
collection.service,
|
||||||
|
collection.identifier,
|
||||||
|
collection.properties
|
||||||
|
);
|
||||||
console.log('[Chrono] - Toggled calendar visibility:', collection);
|
console.log('[Chrono] - Toggled calendar visibility:', collection);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Chrono] - Failed to toggle calendar visibility:', error);
|
console.error('[Chrono] - Failed to toggle calendar visibility:', error);
|
||||||
@@ -123,8 +130,8 @@ function selectTaskList(list: CollectionObject) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTaskList() {
|
function createTaskList() {
|
||||||
editingCollection.value = collectionsStore.fresh();
|
editingCollection.value = new CollectionObject();
|
||||||
editingCollection.value.contents = { task: true };
|
editingCollection.value.properties.contents = { task: true };
|
||||||
collectionEditorMode.value = 'create';
|
collectionEditorMode.value = 'create';
|
||||||
collectionEditorType.value = 'tasklist';
|
collectionEditorType.value = 'tasklist';
|
||||||
showCollectionEditor.value = true;
|
showCollectionEditor.value = true;
|
||||||
@@ -148,9 +155,8 @@ function createEvent() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create fresh event entity
|
selectedEntity.value = new EntityObject();
|
||||||
selectedEntity.value = entitiesStore.fresh('event');
|
selectedEntity.value.properties = new EventObject();
|
||||||
selectedEntity.value.in = selectedCollection.value.id;
|
|
||||||
entityEditorMode.value = 'edit';
|
entityEditorMode.value = 'edit';
|
||||||
showEventEditor.value = true;
|
showEventEditor.value = true;
|
||||||
}
|
}
|
||||||
@@ -171,15 +177,25 @@ async function saveEvent(entity: EntityObject, collection?: CollectionObject | n
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.data) {
|
const eventData = entity.properties as EventObject;
|
||||||
entity.data.modified = new Date();
|
eventData.modified = new Date().toISOString();
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.id === null) {
|
if (!entity.identifier) {
|
||||||
entity.data.created = new Date();
|
eventData.created = new Date().toISOString();
|
||||||
selectedEntity.value = await entitiesStore.create(collection, entity);
|
selectedEntity.value = await entitiesStore.create(
|
||||||
|
collection.provider,
|
||||||
|
collection.service,
|
||||||
|
collection.identifier,
|
||||||
|
eventData.toJson()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
selectedEntity.value = await entitiesStore.modify(collection, entity);
|
selectedEntity.value = await entitiesStore.update(
|
||||||
|
collection.provider,
|
||||||
|
collection.service,
|
||||||
|
collection.identifier,
|
||||||
|
entity.identifier,
|
||||||
|
eventData.toJson()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
entityEditorMode.value = 'view';
|
entityEditorMode.value = 'view';
|
||||||
@@ -191,14 +207,14 @@ async function saveEvent(entity: EntityObject, collection?: CollectionObject | n
|
|||||||
async function deleteEvent(entity: EntityObject, collection?: CollectionObject | null) {
|
async function deleteEvent(entity: EntityObject, collection?: CollectionObject | null) {
|
||||||
try {
|
try {
|
||||||
if (!(collection instanceof CollectionObject)) {
|
if (!(collection instanceof CollectionObject)) {
|
||||||
collection = collections.value.find(c => c.id === entity.in);
|
collection = collections.value.find(c => c.identifier === entity.collection);
|
||||||
}
|
}
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
console.error('[Chrono] - No collection found');
|
console.error('[Chrono] - No collection found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await entitiesStore.destroy(collection, entity);
|
await entitiesStore.delete(collection.provider, collection.service, collection.identifier, entity.identifier);
|
||||||
selectedEntity.value = null;
|
selectedEntity.value = null;
|
||||||
entityEditorMode.value = 'view';
|
entityEditorMode.value = 'view';
|
||||||
showEventEditor.value = false;
|
showEventEditor.value = false;
|
||||||
@@ -218,12 +234,11 @@ function handleDateClick(date: Date) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedEntity.value = entitiesStore.fresh('event');
|
selectedEntity.value = new EntityObject();
|
||||||
selectedEntity.value.in = selectedCollection.value.id;
|
selectedEntity.value.properties = new EventObject();
|
||||||
if (selectedEntity.value.data) {
|
const eventData = selectedEntity.value.properties as EventObject;
|
||||||
selectedEntity.value.data.startsOn = date;
|
eventData.startsOn = date.toISOString();
|
||||||
selectedEntity.value.data.endsOn = new Date(date.getTime() + 60 * 60 * 1000);
|
eventData.endsOn = new Date(date.getTime() + 60 * 60 * 1000).toISOString();
|
||||||
}
|
|
||||||
entityEditorMode.value = 'edit';
|
entityEditorMode.value = 'edit';
|
||||||
showEventEditor.value = true;
|
showEventEditor.value = true;
|
||||||
}
|
}
|
||||||
@@ -257,9 +272,8 @@ function createTask() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create fresh task entity
|
selectedEntity.value = new EntityObject();
|
||||||
selectedEntity.value = entitiesStore.fresh('task');
|
selectedEntity.value.properties = new TaskObject();
|
||||||
selectedEntity.value.in = selectedCollection.value.id;
|
|
||||||
entityEditorMode.value = 'edit';
|
entityEditorMode.value = 'edit';
|
||||||
showTaskEditor.value = true;
|
showTaskEditor.value = true;
|
||||||
}
|
}
|
||||||
@@ -280,15 +294,25 @@ async function saveTask(entity: EntityObject, collection?: CollectionObject | nu
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.data) {
|
const taskData = entity.properties as TaskObject;
|
||||||
entity.data.modified = new Date();
|
taskData.modified = new Date().toISOString();
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.id === null) {
|
if (!entity.identifier) {
|
||||||
entity.data.created = new Date();
|
taskData.created = new Date().toISOString();
|
||||||
selectedEntity.value = await entitiesStore.create(collection, entity);
|
selectedEntity.value = await entitiesStore.create(
|
||||||
|
collection.provider,
|
||||||
|
collection.service,
|
||||||
|
collection.identifier,
|
||||||
|
taskData.toJson()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
selectedEntity.value = await entitiesStore.modify(collection, entity);
|
selectedEntity.value = await entitiesStore.update(
|
||||||
|
collection.provider,
|
||||||
|
collection.service,
|
||||||
|
collection.identifier,
|
||||||
|
entity.identifier,
|
||||||
|
taskData.toJson()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
entityEditorMode.value = 'view';
|
entityEditorMode.value = 'view';
|
||||||
@@ -300,14 +324,14 @@ async function saveTask(entity: EntityObject, collection?: CollectionObject | nu
|
|||||||
async function deleteTask(entity: EntityObject, collection?: CollectionObject | null) {
|
async function deleteTask(entity: EntityObject, collection?: CollectionObject | null) {
|
||||||
try {
|
try {
|
||||||
if (!(collection instanceof CollectionObject)) {
|
if (!(collection instanceof CollectionObject)) {
|
||||||
collection = collections.value.find(c => c.id === entity.in);
|
collection = collections.value.find(c => c.identifier === entity.collection);
|
||||||
}
|
}
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
console.error('[Chrono] - No collection found');
|
console.error('[Chrono] - No collection found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await entitiesStore.destroy(collection, entity);
|
await entitiesStore.delete(collection.provider, collection.service, collection.identifier, entity.identifier);
|
||||||
selectedEntity.value = null;
|
selectedEntity.value = null;
|
||||||
entityEditorMode.value = 'view';
|
entityEditorMode.value = 'view';
|
||||||
showTaskEditor.value = false;
|
showTaskEditor.value = false;
|
||||||
@@ -318,21 +342,27 @@ async function deleteTask(entity: EntityObject, collection?: CollectionObject |
|
|||||||
|
|
||||||
async function toggleTaskComplete(taskId: string | number) {
|
async function toggleTaskComplete(taskId: string | number) {
|
||||||
try {
|
try {
|
||||||
const entity = entities.value.find(e => e.id === taskId);
|
const entity = entities.value.find(e => e.identifier === taskId);
|
||||||
if (!entity || !entity.data) return;
|
if (!entity || !entity.properties) return;
|
||||||
|
|
||||||
const collection = collections.value.find(c => c.id === entity.in);
|
const collection = collections.value.find(c => c.identifier === entity.collection);
|
||||||
if (!collection) return;
|
if (!collection) return;
|
||||||
|
|
||||||
const taskData = entity.data as any;
|
const taskData = entity.properties as TaskObject;
|
||||||
const isCompleted = taskData.status === 'completed';
|
const isCompleted = taskData.status === 'completed';
|
||||||
|
|
||||||
taskData.status = isCompleted ? 'needs-action' : 'completed';
|
taskData.status = isCompleted ? 'needs-action' : 'completed';
|
||||||
taskData.completedOn = isCompleted ? null : new Date();
|
taskData.completedOn = isCompleted ? null : new Date().toISOString();
|
||||||
taskData.progress = isCompleted ? null : 100;
|
taskData.progress = isCompleted ? null : 100;
|
||||||
taskData.modified = new Date();
|
taskData.modified = new Date().toISOString();
|
||||||
|
|
||||||
await entitiesStore.modify(collection, entity);
|
await entitiesStore.update(
|
||||||
|
collection.provider,
|
||||||
|
collection.service,
|
||||||
|
collection.identifier,
|
||||||
|
entity.identifier,
|
||||||
|
taskData.toJson()
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Chrono] - Failed to toggle task completion:', error);
|
console.error('[Chrono] - Failed to toggle task completion:', error);
|
||||||
}
|
}
|
||||||
@@ -341,14 +371,24 @@ async function toggleTaskComplete(taskId: string | number) {
|
|||||||
async function saveCollection(collection: CollectionObject, service: ServiceObject) {
|
async function saveCollection(collection: CollectionObject, service: ServiceObject) {
|
||||||
try {
|
try {
|
||||||
if (collectionEditorMode.value === 'create') {
|
if (collectionEditorMode.value === 'create') {
|
||||||
await collectionsStore.create(service, collection);
|
await collectionsStore.create(
|
||||||
|
service.provider,
|
||||||
|
service.identifier || '',
|
||||||
|
null,
|
||||||
|
collection.properties
|
||||||
|
);
|
||||||
console.log('[Chrono] - Created collection:', collection);
|
console.log('[Chrono] - Created collection:', collection);
|
||||||
} else {
|
} else {
|
||||||
await collectionsStore.modify(collection);
|
await collectionsStore.update(
|
||||||
|
collection.provider,
|
||||||
|
collection.service,
|
||||||
|
collection.identifier,
|
||||||
|
collection.properties
|
||||||
|
);
|
||||||
console.log('[Chrono] - Modified collection:', collection);
|
console.log('[Chrono] - Modified collection:', collection);
|
||||||
}
|
}
|
||||||
// Reload collections
|
// Reload collections
|
||||||
collections.value = await collectionsStore.list();
|
collections.value = Object.values(await collectionsStore.list());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Chrono] - Failed to save collection:', error);
|
console.error('[Chrono] - Failed to save collection:', error);
|
||||||
}
|
}
|
||||||
@@ -356,11 +396,11 @@ async function saveCollection(collection: CollectionObject, service: ServiceObje
|
|||||||
|
|
||||||
async function deleteCollection(collection: CollectionObject) {
|
async function deleteCollection(collection: CollectionObject) {
|
||||||
try {
|
try {
|
||||||
await collectionsStore.destroy(collection);
|
await collectionsStore.delete(collection.provider, collection.service, collection.identifier);
|
||||||
console.log('[Chrono] - Deleted collection:', collection);
|
console.log('[Chrono] - Deleted collection:', collection);
|
||||||
// Reload collections
|
// Reload collections
|
||||||
collections.value = await collectionsStore.list();
|
collections.value = Object.values(await collectionsStore.list());
|
||||||
if (selectedCollection.value?.id === collection.id) {
|
if (selectedCollection.value?.identifier === collection.identifier) {
|
||||||
selectedCollection.value = null;
|
selectedCollection.value = null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -372,10 +412,28 @@ async function deleteCollection(collection: CollectionObject) {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
// Load collections (calendars and task lists)
|
// Load collections (calendars and task lists)
|
||||||
collections.value = await collectionsStore.list();
|
collections.value = Object.values(await collectionsStore.list());
|
||||||
|
|
||||||
// Load entities (events and tasks)
|
// Load entities (events and tasks) for available collection sources only
|
||||||
entities.value = await entitiesStore.list(null, null, null);
|
const sources = collections.value.reduce<Record<string, Record<string, Record<string | number, true>>>>((acc, collection) => {
|
||||||
|
if (!acc[collection.provider]) {
|
||||||
|
acc[collection.provider] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceKey = String(collection.service);
|
||||||
|
if (!acc[collection.provider][serviceKey]) {
|
||||||
|
acc[collection.provider][serviceKey] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[collection.provider][serviceKey][collection.identifier] = true;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
if (Object.keys(sources).length > 0) {
|
||||||
|
entities.value = Object.values(await entitiesStore.list(sources));
|
||||||
|
} else {
|
||||||
|
entities.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[Chrono] - Loaded data from ChronoManager:', {
|
console.log('[Chrono] - Loaded data from ChronoManager:', {
|
||||||
collections: collections.value.length,
|
collections: collections.value.length,
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ export function daysDiff(a: Date, b: Date): number {
|
|||||||
* Check if an event spans multiple days
|
* Check if an event spans multiple days
|
||||||
*/
|
*/
|
||||||
export function isMultiDay(entity: any): boolean {
|
export function isMultiDay(entity: any): boolean {
|
||||||
if (!entity.data?.startsOn) return false;
|
if (!entity.properties?.startsOn) return false;
|
||||||
const start = startOfDay(new Date(entity.data.startsOn));
|
const start = startOfDay(new Date(entity.properties.startsOn));
|
||||||
const end = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : start;
|
const end = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : start;
|
||||||
return daysDiff(start, end) > 0;
|
return daysDiff(start, end) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,10 +48,10 @@ export function isMultiDay(entity: any): boolean {
|
|||||||
* Check if an event is an all-day event (no specific time, or spans full day)
|
* Check if an event is an all-day event (no specific time, or spans full day)
|
||||||
*/
|
*/
|
||||||
export function isAllDay(entity: any): boolean {
|
export function isAllDay(entity: any): boolean {
|
||||||
if (!entity.data?.startsOn) return false;
|
if (!entity.properties?.startsOn) return false;
|
||||||
|
|
||||||
// Explicit all-day flag
|
// Explicit all-day flag
|
||||||
if (entity.data?.allDay === true) return true;
|
if (entity.properties?.timeless === true) return true;
|
||||||
|
|
||||||
// Multi-day events are treated as all-day in the days view
|
// Multi-day events are treated as all-day in the days view
|
||||||
if (isMultiDay(entity)) return true;
|
if (isMultiDay(entity)) return true;
|
||||||
@@ -63,9 +63,9 @@ export function isAllDay(entity: any): boolean {
|
|||||||
* Check if an event overlaps with a date range
|
* Check if an event overlaps with a date range
|
||||||
*/
|
*/
|
||||||
export function eventOverlapsRange(entity: any, rangeStart: Date, rangeEnd: Date): boolean {
|
export function eventOverlapsRange(entity: any, rangeStart: Date, rangeEnd: Date): boolean {
|
||||||
if (!entity.data?.startsOn) return false;
|
if (!entity.properties?.startsOn) return false;
|
||||||
const eventStart = startOfDay(new Date(entity.data.startsOn));
|
const eventStart = startOfDay(new Date(entity.properties.startsOn));
|
||||||
const eventEnd = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : eventStart;
|
const eventEnd = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : eventStart;
|
||||||
return eventStart <= rangeEnd && eventEnd >= rangeStart;
|
return eventStart <= rangeEnd && eventEnd >= rangeStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,9 +73,9 @@ export function eventOverlapsRange(entity: any, rangeStart: Date, rangeEnd: Date
|
|||||||
* Check if an event occurs on a specific date
|
* Check if an event occurs on a specific date
|
||||||
*/
|
*/
|
||||||
export function eventOnDate(entity: any, date: Date): boolean {
|
export function eventOnDate(entity: any, date: Date): boolean {
|
||||||
if (!entity.data?.startsOn) return false;
|
if (!entity.properties?.startsOn) return false;
|
||||||
const eventStart = startOfDay(new Date(entity.data.startsOn));
|
const eventStart = startOfDay(new Date(entity.properties.startsOn));
|
||||||
const eventEnd = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : eventStart;
|
const eventEnd = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : eventStart;
|
||||||
const targetDate = startOfDay(date);
|
const targetDate = startOfDay(date);
|
||||||
return eventStart <= targetDate && eventEnd >= targetDate;
|
return eventStart <= targetDate && eventEnd >= targetDate;
|
||||||
}
|
}
|
||||||
@@ -95,17 +95,17 @@ export function getMultiDaySegments(
|
|||||||
|
|
||||||
// Filter multi-day/all-day events that overlap this range
|
// Filter multi-day/all-day events that overlap this range
|
||||||
const multiDayEvents = events.filter(entity => {
|
const multiDayEvents = events.filter(entity => {
|
||||||
if (!entity.data?.startsOn) return false;
|
if (!entity.properties?.startsOn) return false;
|
||||||
if (!isAllDay(entity)) return false;
|
if (!isAllDay(entity)) return false;
|
||||||
return eventOverlapsRange(entity, rangeStart, rangeEnd);
|
return eventOverlapsRange(entity, rangeStart, rangeEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort: longer events first, then by start date
|
// Sort: longer events first, then by start date
|
||||||
multiDayEvents.sort((a, b) => {
|
multiDayEvents.sort((a, b) => {
|
||||||
const aStart = new Date(a.data.startsOn);
|
const aStart = new Date(a.properties.startsOn);
|
||||||
const aEnd = new Date(a.data.endsOn || a.data.startsOn);
|
const aEnd = new Date(a.properties.endsOn || a.properties.startsOn);
|
||||||
const bStart = new Date(b.data.startsOn);
|
const bStart = new Date(b.properties.startsOn);
|
||||||
const bEnd = new Date(b.data.endsOn || b.data.startsOn);
|
const bEnd = new Date(b.properties.endsOn || b.properties.startsOn);
|
||||||
const aDuration = daysDiff(aStart, aEnd);
|
const aDuration = daysDiff(aStart, aEnd);
|
||||||
const bDuration = daysDiff(bStart, bEnd);
|
const bDuration = daysDiff(bStart, bEnd);
|
||||||
if (bDuration !== aDuration) return bDuration - aDuration;
|
if (bDuration !== aDuration) return bDuration - aDuration;
|
||||||
@@ -116,8 +116,8 @@ export function getMultiDaySegments(
|
|||||||
const lanes: boolean[][] = [];
|
const lanes: boolean[][] = [];
|
||||||
|
|
||||||
for (const entity of multiDayEvents) {
|
for (const entity of multiDayEvents) {
|
||||||
const eventStart = startOfDay(new Date(entity.data.startsOn));
|
const eventStart = startOfDay(new Date(entity.properties.startsOn));
|
||||||
const eventEnd = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : eventStart;
|
const eventEnd = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : eventStart;
|
||||||
|
|
||||||
// Clamp to visible range
|
// Clamp to visible range
|
||||||
const segStart = eventStart < rangeStart ? rangeStart : eventStart;
|
const segStart = eventStart < rangeStart ? rangeStart : eventStart;
|
||||||
@@ -173,9 +173,9 @@ export function getMultiDaySegments(
|
|||||||
*/
|
*/
|
||||||
export function getTimedEventsForDate(events: any[], date: Date): any[] {
|
export function getTimedEventsForDate(events: any[], date: Date): any[] {
|
||||||
return events.filter(entity => {
|
return events.filter(entity => {
|
||||||
if (!entity.data?.startsOn) return false;
|
if (!entity.properties?.startsOn) return false;
|
||||||
if (isAllDay(entity)) return false;
|
if (isAllDay(entity)) return false;
|
||||||
const eventDate = startOfDay(new Date(entity.data.startsOn));
|
const eventDate = startOfDay(new Date(entity.properties.startsOn));
|
||||||
return eventDate.toDateString() === date.toDateString();
|
return eventDate.toDateString() === date.toDateString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -185,9 +185,9 @@ export function getTimedEventsForDate(events: any[], date: Date): any[] {
|
|||||||
*/
|
*/
|
||||||
export function getSingleDayEventsForDate(events: any[], date: Date): any[] {
|
export function getSingleDayEventsForDate(events: any[], date: Date): any[] {
|
||||||
return events.filter(entity => {
|
return events.filter(entity => {
|
||||||
if (!entity.data?.startsOn) return false;
|
if (!entity.properties?.startsOn) return false;
|
||||||
if (isMultiDay(entity)) return false;
|
if (isMultiDay(entity)) return false;
|
||||||
const eventDate = startOfDay(new Date(entity.data.startsOn));
|
const eventDate = startOfDay(new Date(entity.properties.startsOn));
|
||||||
return eventDate.toDateString() === date.toDateString();
|
return eventDate.toDateString() === date.toDateString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user