diff --git a/src/components/Schedule/index.tsx b/src/components/Schedule/index.tsx index f5bf301..f4e54cd 100644 --- a/src/components/Schedule/index.tsx +++ b/src/components/Schedule/index.tsx @@ -9,13 +9,16 @@ import { Button, IconButton, } from '@mui/material'; -import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; +import { FileDownload, CalendarMonth } from '@mui/icons-material'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; import EventsCalendar from './EventsCalendar'; -import generateICSFile from '@/utils/generateICSFile'; +import { + generateICSFile, + generateCombinedICSFile, +} from '@/utils/generateICSFile'; interface ScheduleBaseProps { events: DonationEvent[]; @@ -49,6 +52,7 @@ const ScheduleBase = ({ events, title, description }: ScheduleBaseProps) => { {title} @@ -68,12 +72,26 @@ const ScheduleBase = ({ events, title, description }: ScheduleBaseProps) => { /> - - Events for {selectedDate?.format('MMMM D, YYYY')}: - + + Events for {selectedDate?.format('MMMM D, YYYY')}: + + + + {eventsForSelectedDate.length > 0 ? ( eventsForSelectedDate.map((event_) => ( { onClick={() => generateICSFile(event_)} sx={{ fontSize: 12, display: 'flex', flexDirection: 'column' }} > - - Add to Calendar + + Export to Calendar )) diff --git a/src/utils/generateICSFile.ts b/src/utils/generateICSFile.ts index 17117a2..9837298 100644 --- a/src/utils/generateICSFile.ts +++ b/src/utils/generateICSFile.ts @@ -1,10 +1,9 @@ const formatDate = (dateString: string) => { const date = new Date(dateString); - // Replace colons, dashes, and milliseconds return date .toISOString() - .replace(/[:-]/g, '') // Remove colons and dashes - .replace(/\.\d{3}/, ''); // Remove milliseconds + .replace(/[:-]/g, '') + .replace(/\.\d{3}/, ''); }; const createICSContent = (event: DonationEvent) => { @@ -23,7 +22,7 @@ const createICSContent = (event: DonationEvent) => { `UID:${event.eventId}-${formatDate(event.date)}`, 'END:VEVENT', 'END:VCALENDAR', - ].join('\r\n'); // use CRLF for better compatibility + ].join('\r\n'); }; const generateICSFile = (event: DonationEvent) => { @@ -33,11 +32,51 @@ const generateICSFile = (event: DonationEvent) => { }); const link = document.createElement('a'); link.href = globalThis.URL.createObjectURL(blob); - link.setAttribute('download', `donation-event-${event.eventId}.ics`); + link.setAttribute( + 'download', + `openhands-donation-event-${event.eventId}.ics` + ); + document.body.append(link); + link.click(); + link.remove(); + globalThis.URL.revokeObjectURL(link.href); +}; + +const createCombinedICSContent = (events: DonationEvent[]): string => { + return [ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//OpenHands//Combined Donation Events//EN', + ...events.flatMap((event) => { + const endTime = new Date(new Date(event.date).getTime() + 60 * 60 * 1000); + return [ + 'BEGIN:VEVENT', + `DTSTART:${formatDate(event.date)}`, + `DTEND:${formatDate(endTime.toISOString())}`, + `SUMMARY:${event.title}`, + `DESCRIPTION:Items: ${event.supplies + .map((supply) => `${supply.itemName} (${supply.quantityProvided})`) + .join(', ')}`, + `UID:${event.eventId}-${formatDate(event.date)}`, + 'END:VEVENT', + ]; + }), + 'END:VCALENDAR', + ].join('\r\n'); +}; + +const generateCombinedICSFile = (events: DonationEvent[]) => { + const icsContent = createCombinedICSContent(events); + const blob = new Blob([icsContent], { + type: 'text/calendar;charset=utf-8', + }); + const link = document.createElement('a'); + link.href = globalThis.URL.createObjectURL(blob); + link.setAttribute('download', 'openhands-donation-events.ics'); document.body.append(link); link.click(); link.remove(); globalThis.URL.revokeObjectURL(link.href); // Clean up }; -export default generateICSFile; +export { generateICSFile, generateCombinedICSFile };