feat(01-03): decompose organization.tsx and dashboard.tsx into focused sub-components

- Split organization.tsx (1395 lines) into 12 files: AgentRoster, AgentCreator, AgentEditor, GoalList, GoalCreator, GoalEditor, ProjectList, ProjectCreator, ProjectEditor, OrgChart, helpers, orgTreeLayout
- Split dashboard.tsx (1053 lines) into 11 files: Dashboard, HealthCard, MetricsGrid, ActivityFeed, QuickActions, DepartmentOverview, TaskPipeline, SprintProgress, GoalProgress, KanbanBoard, NeedsAttention
- All sub-component files under 300 lines (max: 239 lines)
- Original files replaced with backward-compatible re-exports
- TypeScript compilation passes
This commit is contained in:
ZacharyZhang-NY committed 2026-03-22 01:54:20 -04:00
1 parent ccfdbe73a3
commit 16d10c0814
25 files changed
+2438 -2448

No files matched your search

@@ -0,0 +1,101 @@
import type { AgentRecord } from "@shared/types";
export const CARD_W = 220;
export const CARD_H = 110;
export const GAP_X = 32;
export const GAP_Y = 80;
export const CHART_PADDING = 60;
export interface OrgTreeNode {
agent: AgentRecord;
children: OrgTreeNode[];
}
export interface LayoutNode {
agent: AgentRecord;
x: number;
y: number;
children: LayoutNode[];
}
export function buildOrgTree(agents: AgentRecord[]): OrgTreeNode[] {
const agentIds = new Set(agents.map((a) => a.id));
const childrenMap = new Map<string, AgentRecord[]>();
const roots: AgentRecord[] = [];
for (const agent of agents) {
if (!agent.reportsTo || !agentIds.has(agent.reportsTo)) {
roots.push(agent);
} else {
const list = childrenMap.get(agent.reportsTo) ?? [];
list.push(agent);
childrenMap.set(agent.reportsTo, list);
}
}
function toNode(agent: AgentRecord): OrgTreeNode {
const kids = childrenMap.get(agent.id) ?? [];
return { agent, children: kids.map(toNode) };
}
return roots.map(toNode);
}
export function subtreeWidth(node: OrgTreeNode): number {
if (node.children.length === 0) return CARD_W;
const childrenW = node.children.reduce((sum, c) => sum + subtreeWidth(c), 0);
const gaps = (node.children.length - 1) * GAP_X;
return Math.max(CARD_W, childrenW + gaps);
}
function layoutTree(node: OrgTreeNode, x: number, y: number): LayoutNode {
const totalW = subtreeWidth(node);
const layoutChildren: LayoutNode[] = [];
if (node.children.length > 0) {
const childrenW = node.children.reduce((sum, c) => sum + subtreeWidth(c), 0);
const gaps = (node.children.length - 1) * GAP_X;
let cx = x + (totalW - childrenW - gaps) / 2;
for (const child of node.children) {
const cw = subtreeWidth(child);
layoutChildren.push(layoutTree(child, cx, y + CARD_H + GAP_Y));
cx += cw + GAP_X;
}
}
return { agent: node.agent, x: x + (totalW - CARD_W) / 2, y, children: layoutChildren };
}
export function layoutForest(roots: OrgTreeNode[]): LayoutNode[] {
let x = CHART_PADDING;
return roots.map((root) => {
const w = subtreeWidth(root);
const node = layoutTree(root, x, CHART_PADDING);
x += w + GAP_X;
return node;
});
}
export function flattenLayout(nodes: LayoutNode[]): LayoutNode[] {
const result: LayoutNode[] = [];
function walk(n: LayoutNode) { result.push(n); n.children.forEach(walk); }
nodes.forEach(walk);
return result;
}
export function collectEdges(nodes: LayoutNode[]): Array<{ parent: LayoutNode; child: LayoutNode }> {
const edges: Array<{ parent: LayoutNode; child: LayoutNode }> = [];
function walk(n: LayoutNode) { for (const c of n.children) { edges.push({ parent: n, child: c }); walk(c); } }
nodes.forEach(walk);
return edges;
}
export const statusDotColor: Record<string, string> = {
running: "#22d3ee",
active: "#4ade80",
paused: "#facc15",
idle: "#facc15",
error: "#f87171",
terminated: "#a3a3a3",
};
export const adapterLabels: Record<string, string> = {
claude_local: "Claude",
codex_local: "Codex",
gemini_local: "Gemini",
};