Import Calendar Events
This commit is contained in:
@@ -15,7 +15,7 @@ export default function CalendarViewWeek(
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
|
||||
const hourFormat = new Intl.DateTimeFormat('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit' });
|
||||
const weekDayFormat = new Intl.DateTimeFormat('en-GB', { weekday: 'short' });
|
||||
const weekDayFormat = new Intl.DateTimeFormat('en-GB', { weekday: 'short', day: 'numeric', month: '2-digit' });
|
||||
|
||||
const days = getDaysForWeek(new Date(startDate));
|
||||
|
||||
|
||||
81
components/calendar/ImportEventsModal.tsx
Normal file
81
components/calendar/ImportEventsModal.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useSignal } from '@preact/signals';
|
||||
import { useEffect } from 'preact/hooks';
|
||||
|
||||
import { Calendar } from '/lib/types.ts';
|
||||
|
||||
interface ImportEventsModalProps {
|
||||
isOpen: boolean;
|
||||
calendars: Pick<Calendar, 'id' | 'name' | 'color'>[];
|
||||
onClickImport: (calendarId: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function ImportEventsModal(
|
||||
{ isOpen, calendars, onClickImport, onClose }: ImportEventsModalProps,
|
||||
) {
|
||||
const newCalendarId = useSignal<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
newCalendarId.value = null;
|
||||
} else {
|
||||
newCalendarId.value = calendars[0]!.id;
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section
|
||||
class={`fixed ${isOpen ? 'block' : 'hidden'} z-40 w-screen h-screen inset-0 bg-gray-900 bg-opacity-60`}
|
||||
>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class={`fixed ${
|
||||
newCalendarId.value ? 'block' : 'hidden'
|
||||
} z-50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 bg-slate-600 text-white rounded-md px-8 py-6 drop-shadow-lg`}
|
||||
>
|
||||
<h1 class='text-2xl font-semibold my-5'>Import Events</h1>
|
||||
<section class='py-5 my-2 border-y border-slate-500'>
|
||||
<fieldset class='block mb-2'>
|
||||
<label class='text-slate-300 block pb-1' for='event_calendar'>Calendar</label>
|
||||
<section class='flex items-center justify-between'>
|
||||
<select
|
||||
class='input-field mr-2'
|
||||
name='event_calendar'
|
||||
id='event_calendar'
|
||||
value={newCalendarId.value || ''}
|
||||
onChange={(event) => {
|
||||
newCalendarId.value = event.currentTarget.value;
|
||||
}}
|
||||
>
|
||||
{calendars.map((calendar) => <option value={calendar.id}>{calendar.name}</option>)}
|
||||
</select>
|
||||
<span
|
||||
class={`w-5 h-5 block ${
|
||||
calendars.find((calendar) => calendar.id === newCalendarId.value)?.color
|
||||
} rounded-full`}
|
||||
title={calendars.find((calendar) => calendar.id === newCalendarId.value)?.color}
|
||||
>
|
||||
</span>
|
||||
</section>
|
||||
</fieldset>
|
||||
</section>
|
||||
<footer class='flex justify-between'>
|
||||
<button
|
||||
class='px-5 py-2 bg-slate-600 hover:bg-slate-500 text-white cursor-pointer rounded-md'
|
||||
onClick={() => onClickImport(newCalendarId.value!)}
|
||||
>
|
||||
Choose File
|
||||
</button>
|
||||
<button
|
||||
class='px-5 py-2 bg-slate-600 hover:bg-slate-500 text-white cursor-pointer rounded-md'
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,26 @@
|
||||
import { useSignal } from '@preact/signals';
|
||||
|
||||
import { Calendar, CalendarEvent } from '/lib/types.ts';
|
||||
import { baseUrl, capitalizeWord, formatCalendarEventsToVCalendar } from '/lib/utils.ts';
|
||||
import {
|
||||
baseUrl,
|
||||
capitalizeWord,
|
||||
formatCalendarEventsToVCalendar,
|
||||
parseVCalendarFromTextContents,
|
||||
} from '/lib/utils.ts';
|
||||
import { RequestBody as GetRequestBody, ResponseBody as GetResponseBody } from '/routes/api/calendar/get-events.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 { RequestBody as ImportRequestBody, ResponseBody as ImportResponseBody } from '/routes/api/calendar/import.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';
|
||||
import ImportEventsModal from './ImportEventsModal.tsx';
|
||||
|
||||
interface MainCalendarProps {
|
||||
initialCalendars: Pick<Calendar, 'id' | 'name' | 'color' | 'is_visible'>[];
|
||||
@@ -38,6 +44,9 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
|
||||
const newEventModal = useSignal<{ isOpen: boolean; initialStartDate?: Date; initiallyAllDay?: boolean }>({
|
||||
isOpen: false,
|
||||
});
|
||||
const openImportModal = useSignal<
|
||||
{ isOpen: boolean }
|
||||
>({ isOpen: false });
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long' });
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
@@ -247,6 +256,11 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
|
||||
}
|
||||
|
||||
function onClickImportICS() {
|
||||
openImportModal.value = { isOpen: true };
|
||||
isImportExportOptionsDropdownOpen.value = false;
|
||||
}
|
||||
|
||||
function onClickChooseImportCalendar(calendarId: string) {
|
||||
isImportExportOptionsDropdownOpen.value = false;
|
||||
|
||||
if (isImporting.value) {
|
||||
@@ -266,7 +280,7 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (fileRead) => {
|
||||
reader.onload = async (fileRead) => {
|
||||
const importFileContents = fileRead.target?.result;
|
||||
|
||||
if (!importFileContents || isImporting.value) {
|
||||
@@ -275,24 +289,33 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
|
||||
|
||||
isImporting.value = true;
|
||||
|
||||
// try {
|
||||
// const partialContacts = parseVCardFromTextContents(importFileContents!.toString());
|
||||
openImportModal.value = { isOpen: false };
|
||||
|
||||
// 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;
|
||||
try {
|
||||
const partialCalendarEvents = parseVCalendarFromTextContents(importFileContents!.toString());
|
||||
|
||||
// if (!result.success) {
|
||||
// throw new Error('Failed to import contact!');
|
||||
// }
|
||||
const requestBody: ImportRequestBody = {
|
||||
partialCalendarEvents,
|
||||
calendarIds: visibleCalendars.map((calendar) => calendar.id),
|
||||
calendarView: view,
|
||||
calendarStartDate: startDate,
|
||||
calendarId,
|
||||
};
|
||||
|
||||
// contacts.value = [...result.contacts];
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// }
|
||||
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 file!');
|
||||
}
|
||||
|
||||
calendarEvents.value = [...result.newCalendarEvents];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
isImporting.value = false;
|
||||
};
|
||||
@@ -601,6 +624,15 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
|
||||
onClickDelete={onClickDeleteEvent}
|
||||
onClose={onCloseOpenEvent}
|
||||
/>
|
||||
|
||||
<ImportEventsModal
|
||||
isOpen={openImportModal.value.isOpen}
|
||||
calendars={calendars.value}
|
||||
onClickImport={onClickChooseImportCalendar}
|
||||
onClose={() => {
|
||||
openImportModal.value = { isOpen: false };
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user