import React, { useState, useEffect, lazy, Suspense } from 'react'; const RichTextEditor = lazy(() => import('./RichTextEditor')); interface CalendarEventModalProps { event?: any; // Existing event if editing initialDate?: Date; // If creating new initialStartTime?: string; // If creating new from slot connections: any[]; // To select calendar onClose: () => void; onSave: (eventData: any) => Promise; onDelete?: (eventId: string, calendarId: string) => Promise; } export default function CalendarEventModal({ event, initialDate, initialStartTime, connections, onClose, onSave, onDelete }: CalendarEventModalProps) { // Flatten calendars from connections to get selectable options const availableCalendars = connections .flatMap(conn => (conn.calendars || []).map((cal: any) => ({ ...cal, provider: conn.provider, providerName: conn.provider === 'google' ? 'Google Calendar' : conn.provider === 'apple' ? 'Apple Calendar' : conn.provider === 'synology' ? 'Synology Calendar' : 'Outlook Calendar' }))) .filter((cal: any) => cal.editable); // Only editable calendars const [title, setTitle] = useState(event?.title || ''); const [description, setDescription] = useState(event?.description || ''); const [location, setLocation] = useState(event?.location || ''); const [url, setUrl] = useState(event?.url || ''); const [recurrence, setRecurrence] = useState(event?.recurrence || 'none'); const [calendarId, setCalendarId] = useState(event?.calendarId || (availableCalendars.length > 0 ? availableCalendars[0].id : '')); // Date/Time State // If event exists, use its start/end. // If new, use initialDate + initialStartTime. // Default duration: 1 hour. const getInitialStart = () => { // Support both nested (start.dateTime) and flat (startTime) event formats if (event?.start?.dateTime) return new Date(event.start.dateTime); if (event?.startTime) return new Date(event.startTime); if (initialDate) { const d = new Date(initialDate); if (initialStartTime) { const [h, m] = initialStartTime.split(':').map(Number); d.setHours(h, m, 0, 0); } else { // Default to next hour if no time specified (though usually slot click gives time) const now = new Date(); d.setHours(now.getHours() + 1, 0, 0, 0); } return d; } return new Date(); }; const getInitialEnd = () => { // Support both nested (end.dateTime) and flat (endTime) event formats if (event?.end?.dateTime) return new Date(event.end.dateTime); if (event?.endTime) return new Date(event.endTime); const start = getInitialStart(); return new Date(start.getTime() + 60 * 60 * 1000); // +1 hour }; const [startDate, setStartDate] = useState(getInitialStart()); const [endDate, setEndDate] = useState(getInitialEnd()); const [allDay, setAllDay] = useState(!!event?.allDay); const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [error, setError] = useState(''); const handleSubmit = async () => { if (!title.trim()) { setError('Title is required'); return; } if (!calendarId) { setError('Please select a calendar'); return; } if (endDate <= startDate) { setError('End time must be after start time'); return; } setIsSaving(true); setError(''); try { await onSave({ id: event?.id, title, description, location, url: url || undefined, recurrence: recurrence !== 'none' ? recurrence : undefined, calendarId, allDay, start: { dateTime: startDate.toISOString() }, end: { dateTime: endDate.toISOString() } }); onClose(); } catch (err: any) { console.error(err); setError(err.message || 'Failed to save event'); setIsSaving(false); } }; const [isDeleteConfirming, setIsDeleteConfirming] = useState(false); const handleDelete = async () => { if (!event?.id || !onDelete) return; if (!isDeleteConfirming) { setIsDeleteConfirming(true); setTimeout(() => setIsDeleteConfirming(false), 3000); // Reset after 3 seconds return; } setIsDeleting(true); try { await onDelete(event.id, event.calendarId); onClose(); } catch (err: any) { setError(err.message || 'Failed to delete event'); setIsDeleting(false); setIsDeleteConfirming(false); } }; // Helper to format date for input type="datetime-local" // Format: YYYY-MM-DDThh:mm const toLocalISOString = (date: Date) => { const offset = date.getTimezoneOffset() * 60000; const localISOTime = (new Date(date.getTime() - offset)).toISOString().slice(0, 16); return localISOTime; }; const handleStartDateChange = (val: string) => { const newStart = new Date(val); setStartDate(newStart); // Auto-adjust end date if it becomes before start if (endDate <= newStart) { setEndDate(new Date(newStart.getTime() + 60 * 60 * 1000)); } }; return (
e.stopPropagation()} style={{ maxWidth: '500px' }}>

{event ? 'Edit Event' : 'New Event'}

{error &&
{error}
}
{/* Title */}
setTitle(e.target.value)} placeholder="Event Title" autoFocus style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '1rem' }} />
{/* Calendar Selection */}
{/* All Day Toggle */}
{/* Date/Time */}
handleStartDateChange(e.target.value)} style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} />
setEndDate(new Date(e.target.value))} style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} />
{/* Recurrence */}
{/* Location */}
setLocation(e.target.value)} placeholder="Location (optional)" style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} />
{/* URL */} {(() => { const selectedCal = availableCalendars.find((c: any) => c.id === calendarId); const isUrlDisabled = selectedCal && (selectedCal.provider === 'google' || selectedCal.provider === 'outlook'); return (
setUrl(e.target.value)} placeholder={isUrlDisabled ? 'Not supported by this calendar provider' : 'https://...'} disabled={!!isUrlDisabled} style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px', opacity: isUrlDisabled ? 0.5 : 1, background: isUrlDisabled ? '#f5f5f5' : undefined, cursor: isUrlDisabled ? 'not-allowed' : undefined, }} />
); })()} {/* Description - Rich text */}
setDescription(e.target.value)} placeholder="Notes..." rows={3} style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px', resize: 'vertical' }} /> }>
{event && onDelete && ( )}
); }