import { createSignal, createEffect, onMount, For, Show } from 'solid-js' import { DateRangePicker } from '@/components/ui/DateRangePicker'; import { ModalPortal } from '@/components/ui/ModalPortal'; import { IconCalendar, IconClock, IconPlus, IconChevronLeft, IconChevronRight, IconCheck, IconX, IconAlertTriangle, IconFlag } from '@tabler/icons-solidjs' import { getMockCalendarEvents } from '@/lib/mockData'; import { isDemoMode as isDemoModeEnabled } from '@/lib/demo-mode'; interface CalendarEvent { id: number title: string description?: string start_time: string end_time: string type: 'task' | 'meeting' | 'deadline' | 'reminder' | 'habit' priority: 'low' | 'medium' | 'high' | 'urgent' location?: string is_completed: boolean is_all_day: boolean task?: { id: number title: string } bookmark?: { id: number title: string } note?: { id: number title: string } } interface NewEvent { title: string description: string start_time: string end_time: string type: 'task' | 'meeting' | 'deadline' | 'reminder' | 'habit' priority: 'low' | 'medium' | 'high' | 'urgent' location: string is_all_day: boolean } export function Calendar() { const [upcomingEvents, setUpcomingEvents] = createSignal([]) const [todayEvents, setTodayEvents] = createSignal([]) const [deadlines, setDeadlines] = createSignal([]) const [currentDate, setCurrentDate] = createSignal(new Date()) const [view, setView] = createSignal<'month' | 'week' | 'day'>('month') const [showEventModal, setShowEventModal] = createSignal(false) const [showTaskDetailModal, setShowTaskDetailModal] = createSignal(false) const [selectedTask, setSelectedTask] = createSignal(null) const [currentTime, setCurrentTime] = createSignal(new Date()) const [mappedEvents, setMappedEvents] = createSignal([]) const [newEvent, setNewEvent] = createSignal({ title: '', description: '', start_time: '', end_time: '', type: 'reminder', priority: 'medium', location: '', is_all_day: false }) // Update current time every second createEffect(() => { const timer = setInterval(() => { setCurrentTime(new Date()) }, 1000) return () => clearInterval(timer) }) // Fetch calendar data const fetchCalendarData = async () => { try { const token = localStorage.getItem('token'); if (isDemoModeEnabled()) { // Use mock data in demo mode const mockEvents = getMockCalendarEvents(); const today = new Date(); today.setHours(0, 0, 0, 0); const weekFromNow = new Date(); weekFromNow.setDate(weekFromNow.getDate() + 7); // Map mock events to calendar events and store for calendar grid const mappedEvents: CalendarEvent[] = mockEvents.map(event => ({ id: parseInt(event.id), title: event.title, description: event.description, start_time: event.start, end_time: event.end, type: event.type === 'personal' ? 'reminder' : event.type as 'task' | 'meeting' | 'deadline' | 'reminder' | 'habit', priority: 'medium' as 'low' | 'medium' | 'high' | 'urgent', location: event.location, is_completed: false, is_all_day: event.allDay })); setMappedEvents(mappedEvents); const todayEvents = mappedEvents.filter(event => { const eventDate = new Date(event.start_time); return eventDate.toDateString() === today.toDateString(); }); const upcomingEvents = mappedEvents.filter(event => { const eventDate = new Date(event.start_time); return eventDate >= today && eventDate <= weekFromNow; }); const deadlines = mappedEvents.filter(event => event.type === 'deadline' && new Date(event.start_time) >= today ); setTodayEvents(todayEvents); setUpcomingEvents(upcomingEvents); setDeadlines(deadlines); return; } if (!token) { setMappedEvents([]); setTodayEvents([]); setUpcomingEvents([]); setDeadlines([]); return; } const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } // Fetch all calendar data in parallel const [upcomingRes, todayRes, deadlinesRes] = await Promise.all([ fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/upcoming`, { headers }), fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/today`, { headers }), fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/deadlines`, { headers }) ]) if (upcomingRes.ok) { const upcomingData = await upcomingRes.json() setUpcomingEvents(upcomingData.events || []) } if (todayRes.ok) { const todayData = await todayRes.json() setTodayEvents(todayData.events || []) } if (deadlinesRes.ok) { const deadlinesData = await deadlinesRes.json() setDeadlines(deadlinesData.deadlines || []) } } catch (error) { console.error('Failed to fetch calendar data:', error) if (isDemoModeEnabled()) { const mockEvents = getMockCalendarEvents(); const today = new Date(); today.setHours(0, 0, 0, 0); const weekFromNow = new Date(); weekFromNow.setDate(weekFromNow.getDate() + 7); const mappedEvents: CalendarEvent[] = mockEvents.map(event => ({ id: parseInt(event.id), title: event.title, description: event.description, start_time: event.start, end_time: event.end, type: event.type === 'personal' ? 'reminder' : event.type as 'task' | 'meeting' | 'deadline' | 'reminder' | 'habit', priority: 'medium' as 'low' | 'medium' | 'high' | 'urgent', location: event.location, is_completed: false, is_all_day: event.allDay })); setMappedEvents(mappedEvents); const todayEvents = mappedEvents.filter(event => { const eventDate = new Date(event.start_time); return eventDate.toDateString() === today.toDateString(); }); const upcomingEvents = mappedEvents.filter(event => { const eventDate = new Date(event.start_time); return eventDate >= today && eventDate <= weekFromNow; }); const deadlines = mappedEvents.filter(event => event.type === 'deadline' && new Date(event.start_time) >= today ); setTodayEvents(todayEvents); setUpcomingEvents(upcomingEvents); setDeadlines(deadlines); return; } setMappedEvents([]); setTodayEvents([]); setUpcomingEvents([]); setDeadlines([]); } } onMount(() => { fetchCalendarData() }) const createEvent = async () => { try { if (isDemoModeEnabled()) { // Simulate event creation in demo mode console.log('Creating event (demo mode):', newEvent()); setShowEventModal(false); setNewEvent({ title: '', description: '', start_time: '', end_time: '', type: 'reminder', priority: 'medium', location: '', is_all_day: false }); fetchCalendarData(); return; } const token = localStorage.getItem('token') if (!token) return const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(newEvent()) }) if (response.ok) { setShowEventModal(false) setNewEvent({ title: '', description: '', start_time: '', end_time: '', type: 'reminder', priority: 'medium', location: '', is_all_day: false }) fetchCalendarData() } } catch (error) { console.error('Failed to create event:', error) // Fallback to demo mode behavior setShowEventModal(false); setNewEvent({ title: '', description: '', start_time: '', end_time: '', type: 'reminder', priority: 'medium', location: '', is_all_day: false }); fetchCalendarData(); } } const toggleEventCompletion = async (eventId: number) => { try { if (isDemoModeEnabled()) { // Simulate event completion toggle in demo mode console.log('Toggling event completion (demo mode):', eventId); fetchCalendarData(); return; } const token = localStorage.getItem('token') if (!token) return const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/calendar/${eventId}/toggle-complete`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }) if (response.ok) { fetchCalendarData() } } catch (error) { console.error('Failed to toggle event completion:', error) // Fallback to demo mode behavior fetchCalendarData(); } } const getPriorityColor = (priority: string) => { switch (priority) { case 'urgent': return 'text-primary' case 'high': return 'text-primary' case 'medium': return 'text-primary' case 'low': return 'text-primary' default: return 'text-primary' } } const getTypeColor = (type: string) => { switch (type) { case 'task': return 'bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary' case 'meeting': return 'bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary' case 'deadline': return 'bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary' case 'reminder': return 'bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary' case 'habit': return 'bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary' default: return 'bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary' } } const formatDateTime = (dateString: string) => { const date = new Date(dateString) return date.toLocaleString() } const formatTime = (dateString: string) => { const date = new Date(dateString) return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) } const openEventModal = (date: Date) => { setNewEvent({ title: '', description: '', start_time: date.toISOString().slice(0, 16), end_time: new Date(date.getTime() + 60 * 60 * 1000).toISOString().slice(0, 16), type: 'reminder', priority: 'medium', location: '', is_all_day: false }) setShowEventModal(true) } const navigateMonth = (direction: number) => { const newDate = new Date(currentDate()) newDate.setMonth(newDate.getMonth() + direction) setCurrentDate(newDate) } return (
{/* Header with Current Time */}

Calendar

{currentTime().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })} {currentTime().toLocaleTimeString()}

{/* Calendar View */}

{currentDate().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}

{/* Enhanced Calendar Grid with Events */}
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => (
{day}
))} {/* Calendar days with events */} {Array.from({ length: 35 }, (_, i) => { const date = new Date(currentDate().getFullYear(), currentDate().getMonth(), i - 2) const isToday = date.toDateString() === new Date().toDateString() const isCurrentMonth = date.getMonth() === currentDate().getMonth() // Get events for this day const dayEvents = mappedEvents().filter((event: CalendarEvent) => { const eventDate = new Date(event.start_time); return eventDate.toDateString() === date.toDateString(); }) || []; return (
openEventModal(date)} class={`border border-border rounded-lg p-1 cursor-pointer hover:bg-accent transition-colors relative overflow-hidden h-24 flex flex-col ${ isToday ? 'bg-primary/10 border-primary' : '' } ${!isCurrentMonth ? 'opacity-40' : ''}`} >
{date.getDate()}
{/* Event indicators */}
{dayEvents.slice(0, 3).map((event: CalendarEvent) => (
{ e.stopPropagation(); setSelectedTask(event); setShowTaskDetailModal(true); }} > {event.title.length > 10 ? event.title.substring(0, 10) + '...' : event.title}
))} {dayEvents.length > 3 && (
{ e.stopPropagation(); // Show all events for this day setSelectedTask({ id: date.getTime(), title: `${date.toLocaleDateString()} - All Events`, description: `Total events: ${dayEvents.length}`, start_time: date.toISOString(), end_time: date.toISOString(), type: 'reminder' as const, priority: 'medium' as const, is_completed: false, is_all_day: true }); setShowTaskDetailModal(true); }} > +{dayEvents.length - 3} more
)}
) })}
{/* Sidebar */}
{/* Today's Events */}

Today's Events

0} fallback={

No events today

} > {(event) => (
{ setSelectedTask(event); setShowTaskDetailModal(true); }}>
{event.type}

{event.title}

{formatTime(event.start_time)} - {formatTime(event.end_time)}

{event.location && (

{event.location}

)}
)}
{/* Upcoming Events */}

Upcoming (7 Days)

0} fallback={

No upcoming events

} > {(event) => (
{ setSelectedTask(event); setShowTaskDetailModal(true); }}>
{event.type}

{event.title}

{formatDateTime(event.start_time)}

)}
{/* Deadlines */}

Deadlines

0} fallback={

No upcoming deadlines

} > {(deadline) => (
{deadline.type}

{deadline.title}

{formatDateTime(deadline.start_time)}

)}
{/* Responsive layout for mobile */}
{/* Today's Events - Mobile */}

Today's Events

0} fallback={

No events today

} > {(event) => (
{event.type}

{event.title}

{formatTime(event.start_time)} - {formatTime(event.end_time)}

{event.location && (

{event.location}

)}
)}
{/* Upcoming Events - Mobile */}

Upcoming (7 Days)

0} fallback={

No upcoming events

} > {(event) => (
{event.type}

{event.title}

{formatDateTime(event.start_time)}

)}
{/* Deadlines - Mobile */}

Deadlines

0} fallback={

No upcoming deadlines

} > {(deadline) => (
{deadline.type}

{deadline.title}

{formatDateTime(deadline.start_time)}

)}
{/* Event Creation Modal */}
{ // Close modal only when clicking the backdrop, not the modal content if (e.target === e.currentTarget) { setShowEventModal(false); } }} >

New Event

setNewEvent({ ...newEvent(), title: e.currentTarget.value })} class="w-full px-3 py-2 border border-border rounded-lg bg-background" placeholder="Event title" />