// app/tasks/TaskCard.tsx // Compact card for a single task. Used inside the Kanban columns. "use client"; import type { Task } from "./api"; import { TASK_TYPE_META, PRIORITY_COLORS, PRIORITY_LABEL, DONE_COLLAPSE_DAYS, } from "./columns"; interface TaskCardProps { task: Task; onClick?: () => void; onDragStart?: (e: React.DragEvent) => void; onDragEnd?: (e: React.DragEvent) => void; isDragging?: boolean; } export default function TaskCard({ task, onClick, onDragStart, onDragEnd, isDragging, }: TaskCardProps) { const meta = TASK_TYPE_META[task.task_type]; const priorityClass = PRIORITY_COLORS[task.priority]; const isDone = task.status === "done"; const isOld = isDone && isOlderThanDays(task.completed_at, DONE_COLLAPSE_DAYS); return (

{task.title}

{meta.icon} {meta.label} {PRIORITY_LABEL[task.priority]} {task.retry_count > 0 ? ( retry ×{task.retry_count} ) : null}
{relativeTime(task.created)} {task.duration_ms != null ? ( {(task.duration_ms / 1000).toFixed(1)}s ) : null}
{task.error ? (

❌ {task.error}

) : null}
); } function relativeTime(iso: string | null): string { if (!iso) return "—"; const t = new Date(iso.includes("T") ? iso : iso.replace(" ", "T") + "Z").getTime(); if (isNaN(t)) return "—"; const diff = Date.now() - t; const sec = Math.floor(diff / 1000); if (sec < 60) return `${sec}s ago`; const min = Math.floor(sec / 60); if (min < 60) return `${min}m ago`; const hr = Math.floor(min / 60); if (hr < 24) return `${hr}h ago`; const day = Math.floor(hr / 24); return `${day}d ago`; } function isOlderThanDays(iso: string | null, days: number): boolean { if (!iso) return false; const t = new Date(iso.includes("T") ? iso : iso.replace(" ", "T") + "Z").getTime(); if (isNaN(t)) return false; return Date.now() - t > days * 24 * 60 * 60 * 1000; }