From 6ac7d2a30a90fbc72f7f5fcc105cb2e7dd99d350 Mon Sep 17 00:00:00 2001 From: vidane Date: Sat, 2 May 2026 18:03:51 -0400 Subject: [PATCH] Add src/components/TaskCard.tsx --- src/components/TaskCard.tsx | 148 ++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/components/TaskCard.tsx diff --git a/src/components/TaskCard.tsx b/src/components/TaskCard.tsx new file mode 100644 index 0000000..34166bb --- /dev/null +++ b/src/components/TaskCard.tsx @@ -0,0 +1,148 @@ +'use client'; + +import { useApp, type Task } from './AppProvider'; +import { format, isToday, isTomorrow, isPast, parseISO } from 'date-fns'; + +interface TaskCardProps { + task: Task; + projectColor?: string; + projectName?: string; +} + +const priorityConfig = { + urgent: { color: 'text-[var(--priority-urgent)]', bg: 'bg-[var(--priority-urgent)]/10', label: 'Urgent', icon: '🔴' }, + high: { color: 'text-[var(--priority-high)]', bg: 'bg-[var(--priority-high)]/10', label: 'High', icon: '🟠' }, + medium: { color: 'text-[var(--priority-medium)]', bg: 'bg-[var(--priority-medium)]/10', label: 'Medium', icon: '🟡' }, + low: { color: 'text-[var(--priority-low)]', bg: 'bg-[var(--priority-low)]/10', label: 'Low', icon: '🟢' }, +}; + +export default function TaskCard({ task, projectColor, projectName }: TaskCardProps) { + const { setSelectedTask, setShowEditTaskModal, setEditingTask, refreshTasks } = useApp(); + + const handleToggle = async (e: React.MouseEvent) => { + e.stopPropagation(); + try { + const res = await fetch(`/api/tasks/${task.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'toggle-done' }), + }); + if (res.ok) { + refreshTasks(); + } + } catch (error) { + console.error('Failed to toggle task:', error); + } + }; + + const handleDelete = async (e: React.MouseEvent) => { + e.stopPropagation(); + if (!confirm('Delete this task?')) return; + try { + await fetch(`/api/tasks/${task.id}`, { method: 'DELETE' }); + refreshTasks(); + } catch (error) { + console.error('Failed to delete task:', error); + } + }; + + const handleClick = () => { + setSelectedTask(task); + setEditingTask(task); + setShowEditTaskModal(true); + }; + + const priority = priorityConfig[task.priority]; + const dueDate = task.dueDate ? parseISO(task.dueDate) : null; + + let dueDateLabel = ''; + let dueDateClass = ''; + if (dueDate) { + if (isToday(dueDate)) { + dueDateLabel = 'Today'; + dueDateClass = 'text-[var(--priority-urgent)] font-medium'; + } else if (isTomorrow(dueDate)) { + dueDateLabel = 'Tomorrow'; + dueDateClass = 'text-[var(--priority-high)]'; + } else if (isPast(dueDate) && !task.completed) { + dueDateLabel = format(dueDate, 'MMM d'); + dueDateClass = 'text-[var(--priority-urgent)] font-medium'; + } else { + dueDateLabel = format(dueDate, 'MMM d'); + dueDateClass = 'text-text-secondary'; + } + } + + const hasRecurrence = task.recurrenceRule !== 'none'; + + return ( +
+ {/* Checkbox */} + + + {/* Project indicator */} + {projectColor && ( + + )} + + {/* Task content */} +
+
+ + {task.title} + + {hasRecurrence && 🔄} +
+ {task.description && ( +

{task.description}

+ )} +
+ + {/* Priority badge */} + + {priority.icon} {priority.label} + + + {/* Due date */} + {dueDate && ( + + {dueDateLabel} + + )} + + {/* Status badge */} + {task.status !== 'done' && ( + + {task.status === 'in_progress' ? 'In Progress' : 'Todo'} + + )} + + {/* Actions */} + +
+ ); +}