feat(18-02): create TaskRow component
Renders a single task row with Unicode tree connectors (├── / └──), StatusBadge, inline agent name, and DependencyIndicator for blocked tasks. Entire row is clickable with hover feedback.
This commit is contained in:
73
packages/web/src/components/TaskRow.tsx
Normal file
73
packages/web/src/components/TaskRow.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
|
import { DependencyIndicator } from "@/components/DependencyIndicator";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
/** Task shape as returned by tRPC (Date fields serialized to string over JSON) */
|
||||||
|
export interface SerializedTask {
|
||||||
|
id: string;
|
||||||
|
planId: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
type: string;
|
||||||
|
priority: string;
|
||||||
|
status: string;
|
||||||
|
order: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskRowProps {
|
||||||
|
task: SerializedTask;
|
||||||
|
agentName: string | null;
|
||||||
|
blockedBy: Array<{ name: string; status: string }>;
|
||||||
|
isLast: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TaskRow({
|
||||||
|
task,
|
||||||
|
agentName,
|
||||||
|
blockedBy,
|
||||||
|
isLast,
|
||||||
|
onClick,
|
||||||
|
}: TaskRowProps) {
|
||||||
|
const connector = isLast ? "└──" : "├──";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Task row */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-pointer items-center gap-2 rounded px-2 py-1 hover:bg-accent",
|
||||||
|
!isLast && "border-l-2 border-muted-foreground/20",
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{/* Tree connector */}
|
||||||
|
<span className="shrink-0 font-mono text-sm text-muted-foreground">
|
||||||
|
{connector}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Task name */}
|
||||||
|
<span className="min-w-0 flex-1 truncate text-sm">{task.name}</span>
|
||||||
|
|
||||||
|
{/* Agent assignment */}
|
||||||
|
{agentName && (
|
||||||
|
<span className="shrink-0 text-xs text-blue-500">[{agentName}]</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Status badge */}
|
||||||
|
<StatusBadge status={task.status} className="shrink-0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dependency indicator below the row */}
|
||||||
|
{blockedBy.length > 0 && (
|
||||||
|
<DependencyIndicator
|
||||||
|
blockedBy={blockedBy}
|
||||||
|
type="task"
|
||||||
|
className="ml-6"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user