import { useState } from 'react'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Search, Filter, SortAsc, Calendar, ChevronLeft, ChevronRight } from 'lucide-react'; import { Task } from './TaskCard'; import TaskCard from './TaskCard'; import TimeCompletionModal from './TimeCompletionModal'; import { addDays, format, isSameDay, startOfToday } from 'date-fns'; interface TasksWithCalendarProps { tasks: Task[]; onTaskUpdate?: (taskId: string, updates: Partial) => void; onTaskEdit?: (task: Task) => void; } type SortOption = 'dueDate' | 'priority' | 'title' | 'status'; type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue'; export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: TasksWithCalendarProps) { const [searchQuery, setSearchQuery] = useState(''); const [sortBy, setSortBy] = useState('dueDate'); const [filterBy, setFilterBy] = useState('all'); const [calendarStartDate, setCalendarStartDate] = useState(startOfToday()); const [draggedTask, setDraggedTask] = useState(null); const [hoveredDate, setHoveredDate] = useState(null); const [completionModal, setCompletionModal] = useState<{ isOpen: boolean; task: Task | null }>({ isOpen: false, task: null }); // Get 7 days starting from today for calendar const dates = Array.from({ length: 7 }, (_, i) => addDays(calendarStartDate, i)); const isOverdue = (task: Task) => { if (!task.dueDate) return false; return task.dueDate < new Date() && task.status !== 'done'; }; const filteredAndSortedTasks = tasks .filter(task => { // Search filter const matchesSearch = task.title.toLowerCase().includes(searchQuery.toLowerCase()) || task.description?.toLowerCase().includes(searchQuery.toLowerCase()); if (!matchesSearch) return false; // Status filter switch (filterBy) { case 'overdue': return isOverdue(task); case 'all': return true; default: return task.status === filterBy; } }) .sort((a, b) => { switch (sortBy) { case 'dueDate': if (!a.dueDate && !b.dueDate) return 0; if (!a.dueDate) return 1; if (!b.dueDate) return -1; return a.dueDate.getTime() - b.dueDate.getTime(); case 'priority': const priorityOrder = { high: 3, medium: 2, low: 1 }; return priorityOrder[b.priority] - priorityOrder[a.priority]; case 'title': return a.title.localeCompare(b.title); case 'status': const statusOrder = { todo: 1, inProgress: 2, done: 3 }; return statusOrder[a.status] - statusOrder[b.status]; default: return 0; } }); // Tasks that are NOT on the calendar (no due date or not scheduled) const unscheduledTasks = filteredAndSortedTasks.filter(task => !task.dueDate); const getTasksForDate = (date: Date) => { return filteredAndSortedTasks.filter(task => task.dueDate && isSameDay(task.dueDate, date) ); }; const getFilterCount = (filter: FilterOption) => { switch (filter) { case 'overdue': return tasks.filter(isOverdue).length; case 'all': return tasks.length; default: return tasks.filter(task => task.status === filter).length; } }; const handleDragStart = (taskId: string, e: React.DragEvent) => { setDraggedTask(taskId); e.dataTransfer.effectAllowed = 'move'; console.log('Drag started for task:', taskId); }; const handleDragEnd = () => { setDraggedTask(null); }; const handleDrop = (date: Date, e: React.DragEvent) => { e.preventDefault(); if (draggedTask) { onTaskUpdate?.(draggedTask, { dueDate: date }); console.log(`Task ${draggedTask} moved to`, date.toLocaleDateString()); setDraggedTask(null); } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }; const handleDragEnter = (date: Date) => { if (draggedTask) { setHoveredDate(date); } }; const handleDragLeave = (e: React.DragEvent) => { // Only clear hover if we're leaving the calendar entirely const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX; const y = e.clientY; if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { setHoveredDate(null); } }; const handleMouseMove = (e: React.MouseEvent, date: Date) => { if (draggedTask) { setHoveredDate(date); } }; const navigateCalendar = (direction: 'prev' | 'next') => { const newDate = addDays(calendarStartDate, direction === 'next' ? 7 : -7); setCalendarStartDate(newDate); console.log(`Navigating calendar to ${newDate.toLocaleDateString()}`); }; const handleTaskComplete = (task: Task) => { setCompletionModal({ isOpen: true, task }); }; const handleTimeLogged = (timeSpent: number) => { if (completionModal.task) { onTaskUpdate?.(completionModal.task.id, { status: 'done', timeTracked: (completionModal.task.timeTracked || 0) + timeSpent, isTracking: false }); } setCompletionModal({ isOpen: false, task: null }); }; return (
{/* Header */}

My Tasks

{filteredAndSortedTasks.length} tasks
{/* Search and Filters */}
{ setSearchQuery(e.target.value); console.log('Search query:', e.target.value); }} className="pl-10" data-testid="input-search-tasks" />
{/* Unscheduled Task List */}
{unscheduledTasks.length === 0 ? (

{searchQuery || filterBy !== 'all' ? 'No unscheduled tasks match your filters' : 'No unscheduled tasks. Drag tasks to the calendar below to schedule them!' }

) : ( <>
Unscheduled Tasks - Drag to calendar below to schedule
{unscheduledTasks.map((task) => (
handleDragStart(task.id, e)} onDragEnd={handleDragEnd} className={`cursor-move transition-transform hover-elevate ${ draggedTask === task.id ? 'opacity-50 scale-95' : '' }`} > { onTaskUpdate?.(task.id, { isTracking: true, timeTracked: task.timeTracked }); console.log(`Timer started for ${task.title}`); }} onPause={() => { onTaskUpdate?.(task.id, { isTracking: false }); console.log(`Timer paused for ${task.title}`); }} onEdit={() => { onTaskEdit?.(task); console.log(`Edit task ${task.title}`); }} onStatusChange={(newStatus) => { if (newStatus === 'done') { handleTaskComplete(task); } else { onTaskUpdate?.(task.id, { status: newStatus }); } console.log(`Task ${task.title} status changed to ${newStatus}`); }} isDragging={draggedTask === task.id} />
))} )}
{/* Calendar Section - Fixed at bottom with full width, above bottom nav */}
{/* Calendar Header */}

Next 7 Days

{/* Full Width Calendar Grid - Responsive */}
{dates.map((date, index) => { const dayTasks = getTasksForDate(date); const isToday = isSameDay(date, new Date()); const isWeekend = date.getDay() === 0 || date.getDay() === 6; const isHovered = hoveredDate && isSameDay(date, hoveredDate); // Use important modifier to ensure weekend styling overrides any conflicting CSS const weekendClasses = isWeekend ? '!bg-muted/80 border-muted-foreground/30 shadow-sm' : ''; const todayClasses = isToday ? 'border-primary ring-1 ring-primary ring-offset-1 ring-offset-background' : 'border-border'; const hoverClasses = isHovered && draggedTask ? 'ring-2 ring-primary bg-primary/15 border-primary shadow-lg' : ''; const dragClasses = draggedTask && !isHovered ? 'border-dashed border-primary/30' : ''; const finalClassName = `p-2 min-h-[100px] transition-all border-2 ${todayClasses} ${weekendClasses} ${hoverClasses} ${dragClasses}`; return ( handleDragEnter(date)} onDragLeave={handleDragLeave} onMouseMove={(e) => handleMouseMove(e, date)} onDrop={(e) => handleDrop(date, e)} onClick={() => { console.log('Date selected:', date.toLocaleDateString()); }} data-testid={`calendar-date-${format(date, 'yyyy-MM-dd')}`} >
{format(date, 'EEE')}
{format(date, 'MMM d')}
{dayTasks.slice(0, 2).map((task) => (
handleDragStart(task.id, e)} onDragEnd={handleDragEnd} className={`cursor-move ${draggedTask === task.id ? 'opacity-50' : ''}`} data-testid={`calendar-task-${task.id}`} >
{task.title}
))} {dayTasks.length > 2 && ( +{dayTasks.length - 2} )} {dayTasks.length === 0 && (
{isHovered && draggedTask ? '✓ Drop here' : draggedTask ? 'Drop' : ''}
)}
); })}
{/* Add bottom padding to prevent content overlap */}
{/* Time Completion Modal */} setCompletionModal({ isOpen: false, task: null })} onSave={handleTimeLogged} taskTitle={completionModal.task?.title || ''} />
); }