"use client"; import { useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { TaskIntakeModal } from "@/components/task-intake-modal"; import type { FleetAgent, TaskRecord, TaskStatus, TaskTemplate } from "@/lib/types"; const COLUMNS: TaskStatus[] = ["Backlog", "Todo", "In Progress", "Review", "Done"]; function familyVariant(family: string | null) { if (family === "zeroclaw") { return "success"; } if (family === "direct") { return "warning"; } return "default"; } function dispatchVariant(state: TaskRecord["dispatch_state"]) { return state === "failed" ? "warning" : state === "completed" ? "success" : "secondary"; } export function TasksClient({ initialTasks, agents, templates, }: { initialTasks: TaskRecord[]; agents: FleetAgent[]; templates: TaskTemplate[]; }) { const [tasks, setTasks] = useState(initialTasks); const [isModalOpen, setIsModalOpen] = useState(false); async function refreshData() { const taskResponse = await fetch("/api/tasks"); setTasks((await taskResponse.json()) as TaskRecord[]); } async function patchTask(taskId: number, payload: Partial) { await fetch(`/api/tasks/${taskId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); await refreshData(); } async function dispatchTask(taskId: number) { await fetch(`/api/tasks/${taskId}/dispatch`, { method: "POST" }); await refreshData(); } async function acknowledgeTask(taskId: number) { await fetch(`/api/tasks/${taskId}/ack`, { method: "POST" }); await refreshData(); } return (
Taskboard The board is the primary workspace. Task intake opens as a modal so the board keeps its full visual width.
{tasks.length} total {tasks.filter((task) => task.status === "In Progress").length} active

Task Board

Columns keep a readable width and scroll horizontally when the viewport is narrower than the full board.

{tasks.length} total tasks
{COLUMNS.map((column) => { const columnTasks = tasks.filter((task) => task.status === column); return ( {column} {columnTasks.length} {columnTasks.map((task) => (

{task.title}

{task.priority}

{task.description || "No description"}

{task.family || "manual"} {task.assignee || "Unassigned"} {task.dispatch_state} {task.tags.map((tag) => ( {tag} ))}
Host
{task.target_host || "n/a"}
Channel
{task.target_channel || "n/a"}
{task.result_summary ? (

Latest Result

{task.result_summary}

{task.result_detail ? (

{task.result_detail}

) : null}
) : null}
{task.dispatch_state !== "dispatched" && task.dispatch_state !== "completed" ? ( ) : null} {task.dispatch_state === "dispatched" ? ( ) : null} {task.status !== "Done" ? ( ) : null}
))}
); })}
setIsModalOpen(false)} onCreated={refreshData} />
); }