import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import TaskModal from './Task/TaskModal'; import { Task } from '../entities/Task'; import { Project } from '../entities/Project'; import { deleteTask } from '../utils/tasksService'; import { ChevronLeftIcon, ChevronRightIcon, CalendarIcon, XMarkIcon, ArrowTopRightOnSquareIcon, } from '@heroicons/react/24/outline'; import { format, addWeeks, addDays } from 'date-fns'; import { el, enUS, es, ja, uk, de } from 'date-fns/locale'; import CalendarMonthView from './Calendar/CalendarMonthView'; import CalendarWeekView from './Calendar/CalendarWeekView'; import CalendarDayView from './Calendar/CalendarDayView'; const getLocale = (language: string) => { switch (language) { case 'el': return el; case 'es': return es; case 'jp': return ja; case 'ua': return uk; case 'de': return de; default: return enUS; } }; interface CalendarEvent { id: string; title: string; start: Date; end: Date; type: 'task' | 'event' | 'google'; color?: string; } interface GoogleCalendarStatus { connected: boolean; email?: string; } const Calendar: React.FC = () => { const { t, i18n } = useTranslation(); const [currentDate, setCurrentDate] = useState(new Date()); const [view, setView] = useState<'month' | 'week' | 'day'>('month'); const [googleStatus, setGoogleStatus] = useState({ connected: false, }); const [isConnecting, setIsConnecting] = useState(false); const [isDemoMode, setIsDemoMode] = useState(false); const [events, setEvents] = useState([]); const [isLoadingTasks, setIsLoadingTasks] = useState(false); const [selectedTask, setSelectedTask] = useState(null); const [allTasks, setAllTasks] = useState([]); const [projects, setProjects] = useState([]); const [isTaskModalOpen, setIsTaskModalOpen] = useState(false); const [isEventDetailModalOpen, setIsEventDetailModalOpen] = useState(false); // Dispatch global modal events const locale = getLocale(i18n.language); // Load Google Calendar status and tasks on component mount useEffect(() => { checkGoogleCalendarStatus(); loadTasks(); loadProjects(); // Check URL parameters for demo mode const urlParams = new URLSearchParams(window.location.search); if ( urlParams.get('demo') === 'true' && urlParams.get('connected') === 'true' ) { setGoogleStatus({ connected: true, email: 'demo@example.com' }); setIsDemoMode(true); // Clean up URL window.history.replaceState( {}, document.title, window.location.pathname ); } }, []); const checkGoogleCalendarStatus = async () => { try { const response = await fetch('/api/calendar/status', { credentials: 'include', }); if (response.ok) { const status = await response.json(); setGoogleStatus(status); setIsDemoMode(status.demo || false); } } catch (error) { console.error('Error checking Google Calendar status:', error); } }; const loadTasks = async () => { setIsLoadingTasks(true); try { const response = await fetch('/api/tasks', { credentials: 'include', }); if (response.ok) { const data = await response.json(); // Handle different API response formats let tasks; if (Array.isArray(data)) { tasks = data; } else if (data && Array.isArray(data.tasks)) { tasks = data.tasks; } else if (data && data.data && Array.isArray(data.data)) { tasks = data.data; } else { console.error('Unexpected API response format:', data); tasks = []; } // Store the original tasks for later reference setAllTasks(tasks); const taskEvents = convertTasksToEvents(tasks); setEvents(taskEvents); } else { console.error('Failed to load tasks, status:', response.status); } } catch (error) { console.error('Error loading tasks:', error); } finally { setIsLoadingTasks(false); } }; const convertTasksToEvents = (tasks: any[]): CalendarEvent[] => { const taskEvents: CalendarEvent[] = []; if (!Array.isArray(tasks)) { console.error('convertTasksToEvents received non-array:', tasks); return []; } tasks.forEach((task) => { // Add tasks with due dates if (task.due_date) { const dueDate = new Date(task.due_date); const taskEvent = { id: `task-${task.id}`, title: task.name || task.title || `Task ${task.id}`, start: dueDate, end: new Date(dueDate.getTime() + 60 * 60 * 1000), // 1 hour duration type: 'task' as const, color: task.completed_at ? '#22c55e' : '#ef4444', // Green if completed, red if not }; taskEvents.push(taskEvent); } // Add tasks scheduled for today (if they don't have due_date) if (!task.due_date && task.created_at) { const createdDate = new Date(task.created_at); const today = new Date(); // Show tasks created today on the calendar if (createdDate.toDateString() === today.toDateString()) { const taskEvent = { id: `task-created-${task.id}`, title: `📝 ${task.name || task.title || `Task ${task.id}`}`, start: createdDate, end: new Date(createdDate.getTime() + 30 * 60 * 1000), // 30 min duration type: 'task' as const, color: task.completed_at ? '#22c55e' : '#3b82f6', // Green if completed, blue if not }; taskEvents.push(taskEvent); } } // Always add tasks to calendar for easier debugging if (!task.due_date && !task.created_at) { const taskEvent = { id: `task-fallback-${task.id}`, title: `📌 ${task.name || task.title || `Task ${task.id}`}`, start: new Date(), // Today end: new Date(Date.now() + 30 * 60 * 1000), // 30 min duration type: 'task' as const, color: task.completed_at ? '#22c55e' : '#8b5cf6', // Green if completed, purple if not }; taskEvents.push(taskEvent); } }); return taskEvents; }; const loadProjects = async () => { try { const response = await fetch('/api/projects', { credentials: 'include', }); if (response.ok) { const projectsData = await response.json(); setProjects(Array.isArray(projectsData) ? projectsData : []); } } catch (error) { console.error('Error loading projects:', error); } }; const connectGoogleCalendar = async () => { if (isConnecting) return; setIsConnecting(true); try { const response = await fetch('/api/calendar/auth', { credentials: 'include', }); if (response.ok) { const result = await response.json(); if (result.demo) { // Demo mode - simulate connection setGoogleStatus({ connected: true, email: 'demo@example.com', }); setIsDemoMode(true); } else { // Real Google OAuth - redirect to auth URL window.location.href = result.authUrl; } } else { throw new Error('Failed to get authorization URL'); } } catch (error) { console.error('Error connecting to Google Calendar:', error); alert(t('calendar.connectionError')); } finally { setIsConnecting(false); } }; const disconnectGoogleCalendar = async () => { try { if (isDemoMode) { // Demo mode - just update local state setGoogleStatus({ connected: false }); setIsDemoMode(false); return; } // Real disconnect API call const response = await fetch('/api/calendar/disconnect', { method: 'POST', credentials: 'include', }); if (response.ok) { setGoogleStatus({ connected: false }); } else { throw new Error('Failed to disconnect'); } } catch (error) { console.error('Error disconnecting Google Calendar:', error); alert(t('calendar.disconnectionError')); } }; const navigate = (direction: 'prev' | 'next') => { setCurrentDate((prev) => { if (view === 'month') { const newDate = new Date(prev); if (direction === 'prev') { newDate.setMonth(prev.getMonth() - 1); } else { newDate.setMonth(prev.getMonth() + 1); } return newDate; } else if (view === 'week') { return direction === 'prev' ? addWeeks(prev, -1) : addWeeks(prev, 1); } else { // day return direction === 'prev' ? addDays(prev, -1) : addDays(prev, 1); } }); }; const goToToday = () => { setCurrentDate(new Date()); }; const handleDateClick = () => { // Date click handler - can be used for future functionality }; const handleEventClick = (event: CalendarEvent) => { // Handle task events if (event.type === 'task') { // Extract task ID from event ID const taskId = event.id.replace(/^task(-created|-fallback)?-/, ''); const task = allTasks.find((t) => t.id.toString() === taskId); if (task) { // Convert task to proper Task entity format for TaskModal const taskEntity: Task = { ...task, name: task.name || task.title || `Task ${task.id}`, // Ensure all required Task properties are present priority: task.priority || 'medium', status: task.status || 'not_started', tags: task.tags || [], note: task.note || task.description || '', due_date: task.due_date, created_at: task.created_at, completed_at: task.completed_at, project_id: task.project_id, }; setSelectedTask(taskEntity); setIsEventDetailModalOpen(true); } } }; const handleTimeSlotClick = () => { // Time slot click handler - can be used for future functionality }; const handleEditTask = () => { setIsEventDetailModalOpen(false); setIsTaskModalOpen(true); }; const handleTaskSave = (updatedTask: Task) => { // Update the task in allTasks setAllTasks((prev) => prev.map((t) => (t.id === updatedTask.id ? updatedTask : t)) ); // Refresh calendar loadTasks(); // Close modal setIsTaskModalOpen(false); setSelectedTask(null); }; const handleTaskDelete = async (taskId: number) => { try { await deleteTask(taskId); // Remove task from allTasks setAllTasks((prev) => prev.filter((t) => t.id !== taskId)); // Refresh calendar loadTasks(); // Close modal setIsTaskModalOpen(false); setSelectedTask(null); } catch (error) { console.error('Failed to delete task:', error); } }; const handleCreateProject = async (name: string): Promise => { try { const response = await fetch('/api/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ name, description: '' }), }); if (response.ok) { const newProject = await response.json(); setProjects((prev) => [...prev, newProject]); return newProject; } else { throw new Error('Failed to create project'); } } catch (error) { console.error('Error creating project:', error); throw error; } }; return (
{/* Header */}

{t('sidebar.calendar')}

{format(currentDate, 'MMMM yyyy', { locale })}
{/* View selector */}
{['month', 'week', 'day'].map((viewType) => ( ))}
{/* Navigation */}
{/* Loading indicator */} {isLoadingTasks && (
{t('calendar.loadingTasks')}
)} {/* Calendar view */} {view === 'month' && ( )} {view === 'week' && ( )} {view === 'day' && ( )} {/* Google Calendar Integration Panel */}

{t('calendar.googleIntegration')}

{isDemoMode ? 'Demo mode: Google Calendar integration simulated for testing purposes.' : t('calendar.googleDescription')}

{t('calendar.googleStatus')}: {googleStatus.connected ? ( {t('calendar.connected')} {googleStatus.email && ` (${googleStatus.email})`} ) : ( {t('calendar.notConnected')} )}

{googleStatus.connected ? ( ) : ( )}
{/* Event Details Modal */} {selectedTask && ( { setIsEventDetailModalOpen(false); setSelectedTask(null); }} task={selectedTask} onEditTask={handleEditTask} /> )} {/* Full Task Edit Modal */} {selectedTask && ( { setIsTaskModalOpen(false); setSelectedTask(null); }} task={selectedTask} onSave={handleTaskSave} onDelete={handleTaskDelete} projects={projects} onCreateProject={handleCreateProject} /> )}
); }; // Simple Task Event Details Modal Component interface TaskEventModalProps { isOpen: boolean; task: Task; onClose: () => void; onEditTask: () => void; } const TaskEventModal: React.FC = ({ isOpen, task, onClose, onEditTask, }) => { const { t, i18n } = useTranslation(); const locale = getLocale(i18n.language); if (!isOpen) return null; return (

📋 {t('calendar.taskDetails')}

{/* Task Title */}

{task.name || `Task ${task.id}`}

{/* Task Status */}
{task.completed_at ? `✅ ${t('calendar.completed')}` : `⏳ ${t('calendar.pending')}`}
{/* Due Date */} {task.due_date && (

{format(new Date(task.due_date), 'PPP', { locale: locale, })}

)} {/* Priority */} {task.priority && (
{t(`calendar.${task.priority}`)}
)} {/* Project */} {task.Project?.name && (

{task.Project.name}

)} {/* Area - Note: Area relationship not in Task entity, removing this section */} {/* Note */} {task.note && (

{task.note}

)} {/* Created Date */} {task.created_at && (

{format(new Date(task.created_at), 'PPp', { locale: locale, })}

)}
{/* Action Buttons */}
{t('calendar.goToTasks')}
); }; export default Calendar;