import { useSignal } from '@preact/signals'; import { Calendar, CalendarEvent } from '/lib/types.ts'; import { baseUrl, capitalizeWord } from '/lib/utils.ts'; // import { RequestBody as GetRequestBody, ResponseBody as GetResponseBody } from '/routes/api/calendar/get.tsx'; import { RequestBody as AddRequestBody, ResponseBody as AddResponseBody } from '/routes/api/calendar/add-event.tsx'; import { RequestBody as DeleteRequestBody, ResponseBody as DeleteResponseBody, } from '/routes/api/calendar/delete-event.tsx'; // import { RequestBody as ImportRequestBody, ResponseBody as ImportResponseBody } from '/routes/api/calendar/import-events.tsx'; import CalendarViewDay from './CalendarViewDay.tsx'; import CalendarViewWeek from './CalendarViewWeek.tsx'; import CalendarViewMonth from './CalendarViewMonth.tsx'; import AddEventModal, { NewCalendarEvent } from './AddEventModal.tsx'; import ViewEventModal from './ViewEventModal.tsx'; import SearchEvents from './SearchEvents.tsx'; interface MainCalendarProps { initialCalendars: Pick[]; initialCalendarEvents: CalendarEvent[]; view: 'day' | 'week' | 'month'; startDate: string; } export default function MainCalendar({ initialCalendars, initialCalendarEvents, view, startDate }: MainCalendarProps) { const isAdding = useSignal(false); const isDeleting = useSignal(false); const isExporting = useSignal(false); const isImporting = useSignal(false); const calendars = useSignal[]>(initialCalendars); const isViewOptionsDropdownOpen = useSignal(false); const isImportExportOptionsDropdownOpen = useSignal(false); const calendarEvents = useSignal(initialCalendarEvents); const openEventModal = useSignal< { isOpen: boolean; calendar?: typeof initialCalendars[number]; calendarEvent?: CalendarEvent } >({ isOpen: false }); const newEventModal = useSignal<{ isOpen: boolean; initialStartDate?: Date; initiallyAllDay?: boolean }>({ isOpen: false, }); const dateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long' }); const today = new Date().toISOString().substring(0, 10); function onClickAddEvent(startDate = new Date(), isAllDay = false) { if (newEventModal.value.isOpen) { newEventModal.value = { isOpen: false, }; return; } if (calendars.value.length === 0) { alert('You need to create a calendar first!'); return; } newEventModal.value = { isOpen: true, initialStartDate: startDate, initiallyAllDay: isAllDay, }; } async function onClickSaveNewEvent(newEvent: NewCalendarEvent) { if (isAdding.value) { return; } if (!newEvent) { return; } isAdding.value = true; try { const requestBody: AddRequestBody = { calendarIds: calendars.value.map((calendar) => calendar.id), calendarView: view, calendarStartDate: startDate, calendarId: newEvent.calendar_id, title: newEvent.title, startDate: new Date(newEvent.start_date).toISOString(), endDate: new Date(newEvent.end_date).toISOString(), isAllDay: newEvent.is_all_day, }; const response = await fetch(`/api/calendar/add-event`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as AddResponseBody; if (!result.success) { throw new Error('Failed to add event!'); } calendarEvents.value = [...result.newCalendarEvents]; newEventModal.value = { isOpen: false, }; } catch (error) { console.error(error); } isAdding.value = false; } function onCloseNewEvent() { newEventModal.value = { isOpen: false, }; } function toggleImportExportOptionsDropdown() { isImportExportOptionsDropdownOpen.value = !isImportExportOptionsDropdownOpen.value; } function toggleViewOptionsDropdown() { isViewOptionsDropdownOpen.value = !isViewOptionsDropdownOpen.value; } function onClickOpenEvent(calendarEvent: CalendarEvent) { if (openEventModal.value.isOpen) { openEventModal.value = { isOpen: false, }; return; } const calendar = calendars.value.find((calendar) => calendar.id === calendarEvent.calendar_id)!; openEventModal.value = { isOpen: true, calendar, calendarEvent, }; } async function onClickDeleteEvent(calendarEventId: string) { if (confirm('Are you sure you want to delete this event?')) { if (isDeleting.value) { return; } isDeleting.value = true; try { const requestBody: DeleteRequestBody = { calendarIds: calendars.value.map((calendar) => calendar.id), calendarView: view, calendarStartDate: startDate, calendarEventId, calendarId: calendarEvents.value.find((calendarEvent) => calendarEvent.id === calendarEventId)!.calendar_id, }; const response = await fetch(`/api/calendar/delete-event`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as DeleteResponseBody; if (!result.success) { throw new Error('Failed to delete event!'); } calendarEvents.value = [...result.newCalendarEvents]; } catch (error) { console.error(error); } isDeleting.value = false; openEventModal.value = { isOpen: false }; } } function onCloseOpenEvent() { openEventModal.value = { isOpen: false, }; } function onClickChangeStartDate(changeTo: 'previous' | 'next' | 'today') { const previousDay = new Date(new Date(startDate).setUTCDate(new Date(startDate).getUTCDate() - 1)).toISOString() .substring(0, 10); const nextDay = new Date(new Date(startDate).setUTCDate(new Date(startDate).getUTCDate() + 1)).toISOString() .substring(0, 10); const previousWeek = new Date(new Date(startDate).setUTCDate(new Date(startDate).getUTCDate() - 7)).toISOString() .substring(0, 10); const nextWeek = new Date(new Date(startDate).setUTCDate(new Date(startDate).getUTCDate() + 7)).toISOString() .substring(0, 10); const previousMonth = new Date(new Date(startDate).setUTCMonth(new Date(startDate).getUTCMonth() - 1)).toISOString() .substring(0, 10); const nextMonth = new Date(new Date(startDate).setUTCMonth(new Date(startDate).getUTCMonth() + 1)).toISOString() .substring(0, 10); if (changeTo === 'today') { if (today === startDate) { return; } window.location.href = `/calendar?view=${view}&startDate=${today}`; return; } if (changeTo === 'previous') { let newStartDate = previousMonth; if (view === 'day') { newStartDate = previousDay; } else if (view === 'week') { newStartDate = previousWeek; } if (newStartDate === startDate) { return; } window.location.href = `/calendar?view=${view}&startDate=${newStartDate}`; return; } let newStartDate = nextMonth; if (view === 'day') { newStartDate = nextDay; } else if (view === 'week') { newStartDate = nextWeek; } if (newStartDate === startDate) { return; } window.location.href = `/calendar?view=${view}&startDate=${newStartDate}`; } function onClickChangeView(newView: MainCalendarProps['view']) { if (view === newView) { isViewOptionsDropdownOpen.value = false; return; } window.location.href = `/calendar?view=${newView}&startDate=${startDate}`; } function onClickImportICS() { isImportExportOptionsDropdownOpen.value = false; if (isImporting.value) { return; } const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.click(); fileInput.onchange = (event) => { const files = (event.target as HTMLInputElement)?.files!; const file = files[0]; if (!file) { return; } const reader = new FileReader(); reader.onload = (fileRead) => { const importFileContents = fileRead.target?.result; if (!importFileContents || isImporting.value) { return; } isImporting.value = true; // try { // const partialContacts = parseVCardFromTextContents(importFileContents!.toString()); // const requestBody: ImportRequestBody = { partialContacts, page }; // const response = await fetch(`/api/calendar/import`, { // method: 'POST', // body: JSON.stringify(requestBody), // }); // const result = await response.json() as ImportResponseBody; // if (!result.success) { // throw new Error('Failed to import contact!'); // } // contacts.value = [...result.contacts]; // } catch (error) { // console.error(error); // } isImporting.value = false; }; reader.readAsText(file, 'UTF-8'); }; } function onClickExportICS() { isImportExportOptionsDropdownOpen.value = false; if (isExporting.value) { return; } isExporting.value = true; // const fileName = ['calendars-', new Date().toISOString().substring(0, 19).replace(/:/g, '-'), '.ics'] // .join(''); // try { // const requestBody: GetRequestBody = {}; // const response = await fetch(`/api/calendar/get`, { // method: 'POST', // body: JSON.stringify(requestBody), // }); // const result = await response.json() as GetResponseBody; // if (!result.success) { // throw new Error('Failed to get contact!'); // } // const exportContents = formatContactToVCard([...result.contacts]); // // Add content-type // const vCardContent = ['data:text/vcard; charset=utf-8,', encodeURIComponent(exportContents)].join(''); // // Download the file // const data = vCardContent; // const link = document.createElement('a'); // link.setAttribute('href', data); // link.setAttribute('download', fileName); // link.click(); // link.remove(); // } catch (error) { // console.error(error); // } isExporting.value = false; } const visibleCalendars = calendars.value.filter((calendar) => calendar.is_visible); return ( <>
Manage calendars

{view === 'day' ? ( ) : null} {view === 'week' ? ( ) : null} {view === 'month' ? ( ) : null} {isDeleting.value ? ( <> Deleting... ) : null} {isExporting.value ? ( <> Exporting... ) : null} {isImporting.value ? ( <> Importing... ) : null} {!isDeleting.value && !isExporting.value && !isImporting.value ? <>  : null}
CalDAV URLs:{' '} {baseUrl}/dav/principals/{' '} {baseUrl}/dav/calendars/
); }