- {{ entity.data.description }}
+
+ {{ entity.properties.description }}
-
+
mdi-calendar
- {{ formatDueDate(new Date(entity.data.dueOn)) }}
+ {{ formatDueDate(new Date(entity.properties.dueOn)) }}
- {{ entity.data.priority === 1 ? 'high' : entity.data.priority === 2 ? 'medium' : 'low' }}
+ {{ entity.properties.priority === 1 ? 'high' : entity.properties.priority === 2 ? 'medium' : 'low' }}
{
// Filter by tab
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') {
- filtered = filtered.filter(entity => entity.data?.status === 'completed');
+ filtered = filtered.filter(entity => entity.properties?.status === 'completed');
}
// Filter by priority
if (filterPriority.value) {
const priorityMap = { high: 1, medium: 2, low: 3 };
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
return filtered.sort((a, b) => {
- const aCompleted = a.data?.status === 'completed';
- const bCompleted = b.data?.status === 'completed';
+ const aCompleted = a.properties?.status === 'completed';
+ const bCompleted = b.properties?.status === 'completed';
if (aCompleted !== bCompleted) {
return aCompleted ? 1 : -1;
}
- if (a.data?.dueOn && b.data?.dueOn) {
- return new Date(a.data.dueOn).getTime() - new Date(b.data.dueOn).getTime();
+ if (a.properties?.dueOn && b.properties?.dueOn) {
+ return new Date(a.properties.dueOn).getTime() - new Date(b.properties.dueOn).getTime();
}
- if (a.data?.dueOn) return -1;
- if (b.data?.dueOn) return 1;
+ if (a.properties?.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);
});
});
diff --git a/src/pages/ChronoPage.vue b/src/pages/ChronoPage.vue
index b03e2a7..15848f2 100644
--- a/src/pages/ChronoPage.vue
+++ b/src/pages/ChronoPage.vue
@@ -8,6 +8,8 @@ import { useEntitiesStore } from '@ChronoManager/stores/entitiesStore';
import { useServicesStore } from '@ChronoManager/stores/servicesStore';
import { CollectionObject } from '@ChronoManager/models/collection';
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 type { CalendarView as CalendarViewType } from '@/types';
import CollectionList from '@/components/CollectionList.vue';
@@ -45,19 +47,19 @@ const selectedCollection = ref(null);
// Computed - filter collections and entities
const calendars = computed(() => {
- return collections.value.filter(col => col.contents?.event);
+ return collections.value.filter(col => col.properties.contents?.event);
});
const taskLists = computed(() => {
- return collections.value.filter(col => col.contents?.task);
+ return collections.value.filter(col => col.properties.contents?.task);
});
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(() => {
- 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
@@ -75,11 +77,11 @@ const isTaskView = computed(() => viewMode.value === 'tasks');
const filteredEvents = computed(() => {
const visibleCalendarIds = calendars.value
- .filter(cal => cal.enabled !== false)
- .map(cal => cal.id);
+ .filter(cal => cal.properties.visibility !== false)
+ .map(cal => cal.identifier);
return events.value.filter(event =>
- visibleCalendarIds.includes(event.in)
+ visibleCalendarIds.includes(event.collection)
);
});
@@ -94,8 +96,8 @@ function selectCalendar(calendar: CollectionObject) {
}
function createCalendar() {
- editingCollection.value = collectionsStore.fresh();
- editingCollection.value.contents = { event: true };
+ editingCollection.value = new CollectionObject();
+ editingCollection.value.properties.contents = { event: true };
collectionEditorMode.value = 'create';
collectionEditorType.value = 'calendar';
showCollectionEditor.value = true;
@@ -110,7 +112,12 @@ function editCalendar(collection: CollectionObject) {
async function toggleCalendarVisibility(collection: CollectionObject) {
try {
- await collectionsStore.modify(collection);
+ await collectionsStore.update(
+ collection.provider,
+ collection.service,
+ collection.identifier,
+ collection.properties
+ );
console.log('[Chrono] - Toggled calendar visibility:', collection);
} catch (error) {
console.error('[Chrono] - Failed to toggle calendar visibility:', error);
@@ -123,8 +130,8 @@ function selectTaskList(list: CollectionObject) {
}
function createTaskList() {
- editingCollection.value = collectionsStore.fresh();
- editingCollection.value.contents = { task: true };
+ editingCollection.value = new CollectionObject();
+ editingCollection.value.properties.contents = { task: true };
collectionEditorMode.value = 'create';
collectionEditorType.value = 'tasklist';
showCollectionEditor.value = true;
@@ -148,9 +155,8 @@ function createEvent() {
return;
}
- // Create fresh event entity
- selectedEntity.value = entitiesStore.fresh('event');
- selectedEntity.value.in = selectedCollection.value.id;
+ selectedEntity.value = new EntityObject();
+ selectedEntity.value.properties = new EventObject();
entityEditorMode.value = 'edit';
showEventEditor.value = true;
}
@@ -171,15 +177,25 @@ async function saveEvent(entity: EntityObject, collection?: CollectionObject | n
return;
}
- if (entity.data) {
- entity.data.modified = new Date();
- }
+ const eventData = entity.properties as EventObject;
+ eventData.modified = new Date().toISOString();
- if (entity.id === null) {
- entity.data.created = new Date();
- selectedEntity.value = await entitiesStore.create(collection, entity);
+ if (!entity.identifier) {
+ eventData.created = new Date().toISOString();
+ selectedEntity.value = await entitiesStore.create(
+ collection.provider,
+ collection.service,
+ collection.identifier,
+ eventData.toJson()
+ );
} 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';
@@ -191,14 +207,14 @@ async function saveEvent(entity: EntityObject, collection?: CollectionObject | n
async function deleteEvent(entity: EntityObject, collection?: CollectionObject | null) {
try {
if (!(collection instanceof CollectionObject)) {
- collection = collections.value.find(c => c.id === entity.in);
+ collection = collections.value.find(c => c.identifier === entity.collection);
}
if (!collection) {
console.error('[Chrono] - No collection found');
return;
}
- await entitiesStore.destroy(collection, entity);
+ await entitiesStore.delete(collection.provider, collection.service, collection.identifier, entity.identifier);
selectedEntity.value = null;
entityEditorMode.value = 'view';
showEventEditor.value = false;
@@ -218,12 +234,11 @@ function handleDateClick(date: Date) {
return;
}
- selectedEntity.value = entitiesStore.fresh('event');
- selectedEntity.value.in = selectedCollection.value.id;
- if (selectedEntity.value.data) {
- selectedEntity.value.data.startsOn = date;
- selectedEntity.value.data.endsOn = new Date(date.getTime() + 60 * 60 * 1000);
- }
+ selectedEntity.value = new EntityObject();
+ selectedEntity.value.properties = new EventObject();
+ const eventData = selectedEntity.value.properties as EventObject;
+ eventData.startsOn = date.toISOString();
+ eventData.endsOn = new Date(date.getTime() + 60 * 60 * 1000).toISOString();
entityEditorMode.value = 'edit';
showEventEditor.value = true;
}
@@ -257,9 +272,8 @@ function createTask() {
return;
}
- // Create fresh task entity
- selectedEntity.value = entitiesStore.fresh('task');
- selectedEntity.value.in = selectedCollection.value.id;
+ selectedEntity.value = new EntityObject();
+ selectedEntity.value.properties = new TaskObject();
entityEditorMode.value = 'edit';
showTaskEditor.value = true;
}
@@ -280,15 +294,25 @@ async function saveTask(entity: EntityObject, collection?: CollectionObject | nu
return;
}
- if (entity.data) {
- entity.data.modified = new Date();
- }
+ const taskData = entity.properties as TaskObject;
+ taskData.modified = new Date().toISOString();
- if (entity.id === null) {
- entity.data.created = new Date();
- selectedEntity.value = await entitiesStore.create(collection, entity);
+ if (!entity.identifier) {
+ taskData.created = new Date().toISOString();
+ selectedEntity.value = await entitiesStore.create(
+ collection.provider,
+ collection.service,
+ collection.identifier,
+ taskData.toJson()
+ );
} 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';
@@ -300,14 +324,14 @@ async function saveTask(entity: EntityObject, collection?: CollectionObject | nu
async function deleteTask(entity: EntityObject, collection?: CollectionObject | null) {
try {
if (!(collection instanceof CollectionObject)) {
- collection = collections.value.find(c => c.id === entity.in);
+ collection = collections.value.find(c => c.identifier === entity.collection);
}
if (!collection) {
console.error('[Chrono] - No collection found');
return;
}
- await entitiesStore.destroy(collection, entity);
+ await entitiesStore.delete(collection.provider, collection.service, collection.identifier, entity.identifier);
selectedEntity.value = null;
entityEditorMode.value = 'view';
showTaskEditor.value = false;
@@ -318,21 +342,27 @@ async function deleteTask(entity: EntityObject, collection?: CollectionObject |
async function toggleTaskComplete(taskId: string | number) {
try {
- const entity = entities.value.find(e => e.id === taskId);
- if (!entity || !entity.data) return;
+ const entity = entities.value.find(e => e.identifier === taskId);
+ 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;
- const taskData = entity.data as any;
+ const taskData = entity.properties as TaskObject;
const isCompleted = taskData.status === '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.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) {
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) {
try {
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);
} else {
- await collectionsStore.modify(collection);
+ await collectionsStore.update(
+ collection.provider,
+ collection.service,
+ collection.identifier,
+ collection.properties
+ );
console.log('[Chrono] - Modified collection:', collection);
}
// Reload collections
- collections.value = await collectionsStore.list();
+ collections.value = Object.values(await collectionsStore.list());
} catch (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) {
try {
- await collectionsStore.destroy(collection);
+ await collectionsStore.delete(collection.provider, collection.service, collection.identifier);
console.log('[Chrono] - Deleted collection:', collection);
// Reload collections
- collections.value = await collectionsStore.list();
- if (selectedCollection.value?.id === collection.id) {
+ collections.value = Object.values(await collectionsStore.list());
+ if (selectedCollection.value?.identifier === collection.identifier) {
selectedCollection.value = null;
}
} catch (error) {
@@ -372,10 +412,28 @@ async function deleteCollection(collection: CollectionObject) {
onMounted(async () => {
try {
// Load collections (calendars and task lists)
- collections.value = await collectionsStore.list();
+ collections.value = Object.values(await collectionsStore.list());
- // Load entities (events and tasks)
- entities.value = await entitiesStore.list(null, null, null);
+ // Load entities (events and tasks) for available collection sources only
+ const sources = collections.value.reduce>>>((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:', {
collections: collections.value.length,
diff --git a/src/utils/calendarHelpers.ts b/src/utils/calendarHelpers.ts
index cf4ef33..db5b8b3 100644
--- a/src/utils/calendarHelpers.ts
+++ b/src/utils/calendarHelpers.ts
@@ -38,9 +38,9 @@ export function daysDiff(a: Date, b: Date): number {
* Check if an event spans multiple days
*/
export function isMultiDay(entity: any): boolean {
- if (!entity.data?.startsOn) return false;
- const start = startOfDay(new Date(entity.data.startsOn));
- const end = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : start;
+ if (!entity.properties?.startsOn) return false;
+ const start = startOfDay(new Date(entity.properties.startsOn));
+ const end = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : start;
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)
*/
export function isAllDay(entity: any): boolean {
- if (!entity.data?.startsOn) return false;
+ if (!entity.properties?.startsOn) return false;
// 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
if (isMultiDay(entity)) return true;
@@ -63,9 +63,9 @@ export function isAllDay(entity: any): boolean {
* Check if an event overlaps with a date range
*/
export function eventOverlapsRange(entity: any, rangeStart: Date, rangeEnd: Date): boolean {
- if (!entity.data?.startsOn) return false;
- const eventStart = startOfDay(new Date(entity.data.startsOn));
- const eventEnd = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : eventStart;
+ if (!entity.properties?.startsOn) return false;
+ const eventStart = startOfDay(new Date(entity.properties.startsOn));
+ const eventEnd = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : eventStart;
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
*/
export function eventOnDate(entity: any, date: Date): boolean {
- if (!entity.data?.startsOn) return false;
- const eventStart = startOfDay(new Date(entity.data.startsOn));
- const eventEnd = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : eventStart;
+ if (!entity.properties?.startsOn) return false;
+ const eventStart = startOfDay(new Date(entity.properties.startsOn));
+ const eventEnd = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : eventStart;
const targetDate = startOfDay(date);
return eventStart <= targetDate && eventEnd >= targetDate;
}
@@ -95,17 +95,17 @@ export function getMultiDaySegments(
// Filter multi-day/all-day events that overlap this range
const multiDayEvents = events.filter(entity => {
- if (!entity.data?.startsOn) return false;
+ if (!entity.properties?.startsOn) return false;
if (!isAllDay(entity)) return false;
return eventOverlapsRange(entity, rangeStart, rangeEnd);
});
// Sort: longer events first, then by start date
multiDayEvents.sort((a, b) => {
- const aStart = new Date(a.data.startsOn);
- const aEnd = new Date(a.data.endsOn || a.data.startsOn);
- const bStart = new Date(b.data.startsOn);
- const bEnd = new Date(b.data.endsOn || b.data.startsOn);
+ const aStart = new Date(a.properties.startsOn);
+ const aEnd = new Date(a.properties.endsOn || a.properties.startsOn);
+ const bStart = new Date(b.properties.startsOn);
+ const bEnd = new Date(b.properties.endsOn || b.properties.startsOn);
const aDuration = daysDiff(aStart, aEnd);
const bDuration = daysDiff(bStart, bEnd);
if (bDuration !== aDuration) return bDuration - aDuration;
@@ -116,8 +116,8 @@ export function getMultiDaySegments(
const lanes: boolean[][] = [];
for (const entity of multiDayEvents) {
- const eventStart = startOfDay(new Date(entity.data.startsOn));
- const eventEnd = entity.data?.endsOn ? startOfDay(new Date(entity.data.endsOn)) : eventStart;
+ const eventStart = startOfDay(new Date(entity.properties.startsOn));
+ const eventEnd = entity.properties?.endsOn ? startOfDay(new Date(entity.properties.endsOn)) : eventStart;
// Clamp to visible range
const segStart = eventStart < rangeStart ? rangeStart : eventStart;
@@ -173,9 +173,9 @@ export function getMultiDaySegments(
*/
export function getTimedEventsForDate(events: any[], date: Date): any[] {
return events.filter(entity => {
- if (!entity.data?.startsOn) return false;
+ if (!entity.properties?.startsOn) 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();
});
}
@@ -185,9 +185,9 @@ export function getTimedEventsForDate(events: any[], date: Date): any[] {
*/
export function getSingleDayEventsForDate(events: any[], date: Date): any[] {
return events.filter(entity => {
- if (!entity.data?.startsOn) return false;
+ if (!entity.properties?.startsOn) 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();
});
}