Add live ActiveAgentsPanel with real-time transcript feed, SidebarContext for responsive sidebar state, agent config form with reasoning effort, improved inbox with failed run alerts, enriched issue detail with project picker, and various component refinements across pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
92 lines
2.4 KiB
TypeScript
92 lines
2.4 KiB
TypeScript
import type { Goal } from "@paperclip/shared";
|
|
import { StatusBadge } from "./StatusBadge";
|
|
import { ChevronRight } from "lucide-react";
|
|
import { cn } from "../lib/utils";
|
|
import { useState } from "react";
|
|
|
|
interface GoalTreeProps {
|
|
goals: Goal[];
|
|
onSelect?: (goal: Goal) => void;
|
|
}
|
|
|
|
interface GoalNodeProps {
|
|
goal: Goal;
|
|
children: Goal[];
|
|
allGoals: Goal[];
|
|
depth: number;
|
|
onSelect?: (goal: Goal) => void;
|
|
}
|
|
|
|
function GoalNode({ goal, children, allGoals, depth, onSelect }: GoalNodeProps) {
|
|
const [expanded, setExpanded] = useState(true);
|
|
const hasChildren = children.length > 0;
|
|
|
|
return (
|
|
<div>
|
|
<div
|
|
className={cn(
|
|
"flex items-center gap-2 px-3 py-1.5 text-sm transition-colors cursor-pointer hover:bg-accent/50",
|
|
)}
|
|
style={{ paddingLeft: `${depth * 16 + 12}px` }}
|
|
onClick={() => onSelect?.(goal)}
|
|
>
|
|
{hasChildren ? (
|
|
<button
|
|
className="p-0.5"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setExpanded(!expanded);
|
|
}}
|
|
>
|
|
<ChevronRight
|
|
className={cn("h-3 w-3 transition-transform", expanded && "rotate-90")}
|
|
/>
|
|
</button>
|
|
) : (
|
|
<span className="w-4" />
|
|
)}
|
|
<span className="text-xs text-muted-foreground capitalize">{goal.level}</span>
|
|
<span className="flex-1 truncate">{goal.title}</span>
|
|
<StatusBadge status={goal.status} />
|
|
</div>
|
|
{hasChildren && expanded && (
|
|
<div>
|
|
{children.map((child) => (
|
|
<GoalNode
|
|
key={child.id}
|
|
goal={child}
|
|
children={allGoals.filter((g) => g.parentId === child.id)}
|
|
allGoals={allGoals}
|
|
depth={depth + 1}
|
|
onSelect={onSelect}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function GoalTree({ goals, onSelect }: GoalTreeProps) {
|
|
const roots = goals.filter((g) => !g.parentId);
|
|
|
|
if (goals.length === 0) {
|
|
return <p className="text-sm text-muted-foreground">No goals.</p>;
|
|
}
|
|
|
|
return (
|
|
<div className="border border-border py-1">
|
|
{roots.map((goal) => (
|
|
<GoalNode
|
|
key={goal.id}
|
|
goal={goal}
|
|
children={goals.filter((g) => g.parentId === goal.id)}
|
|
allGoals={goals}
|
|
depth={0}
|
|
onSelect={onSelect}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|