Felles MessageData-type (matcher messages-tabellen) med 3 renderingsmodi: - expanded (chat): forfatter, tid, HTML-body, mention-klikk, badges - compact (kanban): tittel, trunkert preview, fargestripe - calendar (pill): tidspunkt, tittel, bakgrunnsfarge Alle blokker (ChatBlock, KanbanBlock, CalendarBlock) migrert til MessageBox. PG-adaptere mapper API-respons til MessageData med view-spesifikke felter. SpacetimeDB-adapter oppdatert for kompatibilitet. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
147 lines
3.5 KiB
TypeScript
147 lines
3.5 KiB
TypeScript
import type { MessageData } from '$lib/types/message';
|
|
import type { Calendar, CalendarConnection } from './types';
|
|
|
|
interface RawEvent {
|
|
id: string;
|
|
calendar_id: string;
|
|
title: string;
|
|
description: string | null;
|
|
starts_at: string;
|
|
ends_at: string | null;
|
|
all_day: boolean;
|
|
color: string | null;
|
|
linked_node: string | null;
|
|
created_by: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
interface RawCalendar {
|
|
id: string;
|
|
name: string;
|
|
color: string | null;
|
|
events: RawEvent[];
|
|
}
|
|
|
|
function eventToMessageData(event: RawEvent): MessageData {
|
|
return {
|
|
id: event.id,
|
|
channel_id: null,
|
|
reply_to: null,
|
|
author_id: event.created_by,
|
|
author_name: null,
|
|
message_type: 'calendar',
|
|
title: event.title,
|
|
body: event.description ?? '',
|
|
pinned: false,
|
|
visibility: 'workspace',
|
|
created_at: event.created_at,
|
|
updated_at: event.created_at,
|
|
kanban_view: null,
|
|
calendar_view: {
|
|
starts_at: event.starts_at,
|
|
ends_at: event.ends_at,
|
|
all_day: event.all_day,
|
|
color: event.color
|
|
}
|
|
};
|
|
}
|
|
|
|
export function createPgCalendar(calendarId: string): CalendarConnection {
|
|
let _calendar = $state<Calendar | null>(null);
|
|
let _events = $state<MessageData[]>([]);
|
|
let _error = $state('');
|
|
let _loading = $state(true);
|
|
let _viewDate = $state(new Date());
|
|
let _interval: ReturnType<typeof setInterval> | null = null;
|
|
|
|
function getMonthRange(date: Date): { from: string; to: string } {
|
|
const year = date.getFullYear();
|
|
const month = date.getMonth();
|
|
const from = new Date(year, month, -6);
|
|
const to = new Date(year, month + 1, 7);
|
|
return {
|
|
from: from.toISOString(),
|
|
to: to.toISOString()
|
|
};
|
|
}
|
|
|
|
async function fetchEvents() {
|
|
try {
|
|
const { from, to } = getMonthRange(_viewDate);
|
|
const res = await fetch(
|
|
`/api/calendar/${calendarId}?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
|
|
);
|
|
if (!res.ok) {
|
|
_error = `Feil: ${res.status}`;
|
|
return;
|
|
}
|
|
const data: RawCalendar = await res.json();
|
|
const mapped = data.events.map(eventToMessageData);
|
|
_calendar = { id: data.id, name: data.name, color: data.color, events: mapped };
|
|
_events = mapped;
|
|
_error = '';
|
|
} catch (e) {
|
|
_error = e instanceof Error ? e.message : 'Ukjent feil';
|
|
} finally {
|
|
_loading = false;
|
|
}
|
|
}
|
|
|
|
fetchEvents();
|
|
_interval = setInterval(fetchEvents, 5000);
|
|
|
|
return {
|
|
get calendar() { return _calendar; },
|
|
get events() { return _events; },
|
|
get error() { return _error; },
|
|
get loading() { return _loading; },
|
|
get viewDate() { return _viewDate; },
|
|
|
|
setViewDate(date: Date) {
|
|
_viewDate = date;
|
|
_loading = true;
|
|
fetchEvents();
|
|
},
|
|
|
|
async addEvent(event) {
|
|
const res = await fetch(`/api/calendar/${calendarId}/events`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(event)
|
|
});
|
|
if (!res.ok) {
|
|
_error = `Feil: ${res.status}`;
|
|
return;
|
|
}
|
|
await fetchEvents();
|
|
},
|
|
|
|
async updateEvent(eventId, updates) {
|
|
const res = await fetch(`/api/calendar/${calendarId}/events/${eventId}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(updates)
|
|
});
|
|
if (!res.ok) {
|
|
_error = `Feil: ${res.status}`;
|
|
return;
|
|
}
|
|
await fetchEvents();
|
|
},
|
|
|
|
async deleteEvent(eventId) {
|
|
const res = await fetch(`/api/calendar/${calendarId}/events/${eventId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
if (!res.ok) {
|
|
_error = `Feil: ${res.status}`;
|
|
return;
|
|
}
|
|
await fetchEvents();
|
|
},
|
|
|
|
destroy() {
|
|
if (_interval) clearInterval(_interval);
|
|
}
|
|
};
|
|
}
|