// 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;
}