server/web/src/lib/calendar/pg.svelte.ts
vegard e3e3bbc24f MessageBox: universell meldingsboks-komponent for chat, kanban og kalender
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>
2026-03-15 22:15:36 +01:00

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);
}
};
}