"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 { Input } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import type { FleetAgent, TaskEvent, TaskPriority, TaskRecord, TaskStatus, TaskTemplate, } from "@/lib/types"; const COLUMNS: TaskStatus[] = ["Backlog", "Todo", "In Progress", "Review", "Done"]; const PRIORITIES: TaskPriority[] = ["Low", "Medium", "High", "Critical"]; function familyVariant(family: string | null) { return family === "zeroclaw" ? "success" : "default"; } function dispatchVariant(state: TaskRecord["dispatch_state"]) { return state === "failed" ? "warning" : state === "completed" ? "success" : "secondary"; } export function TasksClient({ initialTasks, initialEvents, agents, templates, }: { initialTasks: TaskRecord[]; initialEvents: TaskEvent[]; agents: FleetAgent[]; templates: TaskTemplate[]; }) { const [tasks, setTasks] = useState(initialTasks); const [events, setEvents] = useState(initialEvents); const [formState, setFormState] = useState({ templateKey: "", title: "", description: "", assignee: "", priority: "Medium" as TaskPriority, tags: "", repoSlug: "", baseBranch: "main", preferredAgent: "codex", reasoningEffort: "high", modelHint: "", }); const selectedTemplate = templates.find((template) => template.key === formState.templateKey) || null; const selectedAgent = agents.find((agent) => agent.assignmentKey === formState.assignee) || null; const failedTasks = tasks.filter((task) => task.dispatch_state === "failed"); async function refreshData() { const [taskResponse, eventResponse] = await Promise.all([ fetch("/api/tasks"), fetch("/api/dispatch-history"), ]); setTasks((await taskResponse.json()) as TaskRecord[]); setEvents((await eventResponse.json()) as TaskEvent[]); } function applyTemplate(templateKey: string) { const template = templates.find((entry) => entry.key === templateKey) || null; if (!template) { setFormState((current) => ({ ...current, templateKey })); return; } setFormState((current) => ({ ...current, templateKey, title: current.title || template.title, priority: template.defaults.priority, tags: template.tags.join(", "), repoSlug: template.defaults.repoSlug || current.repoSlug, baseBranch: template.defaults.baseBranch || current.baseBranch, preferredAgent: template.defaults.preferredAgent || current.preferredAgent, reasoningEffort: template.defaults.reasoningEffort || current.reasoningEffort, })); } async function createTask(event: React.FormEvent) { event.preventDefault(); const tags = formState.tags .split(",") .map((tag) => tag.trim()) .filter(Boolean); await fetch("/api/tasks", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: formState.title, description: formState.description, assignee: formState.assignee, priority: formState.priority, tags, template_key: formState.templateKey || null, repo_slug: formState.repoSlug || null, base_branch: formState.baseBranch || null, preferred_agent: formState.preferredAgent || null, reasoning_effort: formState.reasoningEffort || null, model_hint: formState.modelHint || null, family: selectedAgent?.family || selectedTemplate?.family || null, target_host: selectedAgent?.host || selectedTemplate?.defaults.targetHost || "", target_channel: selectedAgent?.channels[0]?.value || selectedTemplate?.defaults.targetChannel || "", dispatch_method: selectedAgent?.defaultDispatchMethod || selectedTemplate?.defaults.dispatchMethod || "manual", }), }); setFormState({ templateKey: "", title: "", description: "", assignee: "", priority: "Medium", tags: "", repoSlug: "", baseBranch: "main", preferredAgent: "codex", reasoningEffort: "high", modelHint: "", }); await refreshData(); } 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 (
Unified Task Intake Create typed tasks, apply dispatch templates, and route work to OpenClaw or ZeroClaw.
setFormState((current) => ({ ...current, title: event.target.value }))} /> setFormState((current) => ({ ...current, repoSlug: event.target.value }))} /> setFormState((current) => ({ ...current, baseBranch: event.target.value }))} /> setFormState((current) => ({ ...current, preferredAgent: event.target.value }))} /> setFormState((current) => ({ ...current, reasoningEffort: event.target.value }))} /> setFormState((current) => ({ ...current, tags: event.target.value }))} /> setFormState((current) => ({ ...current, modelHint: event.target.value }))} />