paperclip/ui/src/pages/Org.tsx
Forgotten adca44849a feat(ui): active agents panel, sidebar context, and page enhancements
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>
2026-02-20 10:32:32 -06:00

128 lines
3.6 KiB
TypeScript

import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { agentsApi, type OrgNode } from "../api/agents";
import { useCompany } from "../context/CompanyContext";
import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { queryKeys } from "../lib/queryKeys";
import { StatusBadge } from "../components/StatusBadge";
import { EmptyState } from "../components/EmptyState";
import { ChevronRight, GitBranch } from "lucide-react";
import { cn } from "../lib/utils";
function OrgTree({
nodes,
depth = 0,
onSelect,
}: {
nodes: OrgNode[];
depth?: number;
onSelect: (id: string) => void;
}) {
return (
<div>
{nodes.map((node) => (
<OrgTreeNode key={node.id} node={node} depth={depth} onSelect={onSelect} />
))}
</div>
);
}
function OrgTreeNode({
node,
depth,
onSelect,
}: {
node: OrgNode;
depth: number;
onSelect: (id: string) => void;
}) {
const [expanded, setExpanded] = useState(true);
const hasChildren = node.reports.length > 0;
return (
<div>
<div
className="flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors cursor-pointer hover:bg-accent/50"
style={{ paddingLeft: `${depth * 16 + 12}px` }}
onClick={() => onSelect(node.id)}
>
{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={cn(
"h-2 w-2 rounded-full shrink-0",
node.status === "active"
? "bg-green-400"
: node.status === "paused"
? "bg-yellow-400"
: node.status === "pending_approval"
? "bg-amber-400"
: node.status === "error"
? "bg-red-400"
: "bg-neutral-400"
)}
/>
<span className="font-medium flex-1">{node.name}</span>
<span className="text-xs text-muted-foreground">{node.role}</span>
<StatusBadge status={node.status} />
</div>
{hasChildren && expanded && (
<OrgTree nodes={node.reports} depth={depth + 1} onSelect={onSelect} />
)}
</div>
);
}
export function Org() {
const { selectedCompanyId } = useCompany();
const { setBreadcrumbs } = useBreadcrumbs();
const navigate = useNavigate();
useEffect(() => {
setBreadcrumbs([{ label: "Org Chart" }]);
}, [setBreadcrumbs]);
const { data, isLoading, error } = useQuery({
queryKey: queryKeys.org(selectedCompanyId!),
queryFn: () => agentsApi.org(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
if (!selectedCompanyId) {
return <EmptyState icon={GitBranch} message="Select a company to view org chart." />;
}
return (
<div className="space-y-4">
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
{error && <p className="text-sm text-destructive">{error.message}</p>}
{data && data.length === 0 && (
<EmptyState
icon={GitBranch}
message="No agents in the organization. Create agents to build your org chart."
/>
)}
{data && data.length > 0 && (
<div className="border border-border py-1">
<OrgTree nodes={data} onSelect={(id) => navigate(`/agents/${id}`)} />
</div>
)}
</div>
);
}