From bb59eb0d45bef59fb093d721e23f46d0f224fcae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 23 Mar 2026 11:54:19 -0400 Subject: [PATCH] refactor(ui): clean up Dashboard from 9 chaotic sections to 4 focused ones Remove HealthRing, DepartmentOverview, TaskPipeline, SprintProgress, GoalProgress, WorkforceGrid, ConnectorHealth. Move Needs Attention to top. Reduce 9 metric cards to 4. Add Active Work section. -50KB bundle. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/renderer/App.tsx | 5 - src/renderer/components/dashboard.tsx | 7 - .../components/dashboard/Dashboard.tsx | 344 +++++++++--------- 3 files changed, 169 insertions(+), 187 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 6e2156bf..a106d75b 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -808,12 +808,7 @@ export function App() { projects={companyProjects} activity={filteredActivity} costs={companyCosts} - connectors={snapshot.connectors} - meetings={companyMeetings} - sprints={companySprints} messages={snapshot.agentMessages?.filter((message) => !currentCompany || message.companyId === currentCompany.id) ?? []} - browserActions={browserActions} - workflows={snapshot.workflows?.filter((workflow) => !currentCompany || workflow.companyId === currentCompany.id) ?? []} onNavigate={navigateTo} /> ) : null} diff --git a/src/renderer/components/dashboard.tsx b/src/renderer/components/dashboard.tsx index 52813b01..7528f833 100644 --- a/src/renderer/components/dashboard.tsx +++ b/src/renderer/components/dashboard.tsx @@ -1,12 +1,5 @@ export { Dashboard } from "./dashboard/Dashboard"; export type { DashboardProps } from "./dashboard/Dashboard"; export { KanbanBoard } from "./dashboard/KanbanBoard"; -export { HealthRing, computeHealthScore } from "./dashboard/HealthCard"; export { MetricCard } from "./dashboard/MetricsGrid"; export { ActivityFeed } from "./dashboard/ActivityFeed"; -export { WorkforceGrid } from "./dashboard/QuickActions"; -export { DepartmentOverview } from "./dashboard/DepartmentOverview"; -export { TaskPipeline } from "./dashboard/TaskPipeline"; -export { SprintProgress } from "./dashboard/SprintProgress"; -export { GoalProgress } from "./dashboard/GoalProgress"; -export { NeedsAttention } from "./dashboard/NeedsAttention"; diff --git a/src/renderer/components/dashboard/Dashboard.tsx b/src/renderer/components/dashboard/Dashboard.tsx index 29f95e5a..4be9db87 100644 --- a/src/renderer/components/dashboard/Dashboard.tsx +++ b/src/renderer/components/dashboard/Dashboard.tsx @@ -2,46 +2,28 @@ import { useMemo } from "react"; import { ArrowRight, Briefcase, - Cpu, CurrencyCircleDollar, - Heartbeat, - Lightning, + Robot, ShieldCheck, - Target, - TreeStructure, - Users, Warning, + XCircle, } from "@phosphor-icons/react"; import type { ActivityRecord, AgentRecord, AgentMessageRecord, ApprovalRecord, - BrowserActionRecord, - ConnectorRecord, CostRecord, GoalRecord, - MeetingRecord, ProjectRecord, RunRecord, SectionId, - SprintRecord, TaskRecord, - WorkflowPipelineRecord, } from "@shared/types"; -import { isConnectorExecutionReady } from "@shared/connector-policy"; import { formatMoney } from "../../lib/formatters"; -import { StatusPill } from "../ui"; import { useT } from "../../i18n"; -import { computeHealthScore, HealthRing } from "./HealthCard"; import { MetricCard } from "./MetricsGrid"; import { ActivityFeed } from "./ActivityFeed"; -import { WorkforceGrid } from "./QuickActions"; -import { DepartmentOverview } from "./DepartmentOverview"; -import { TaskPipeline } from "./TaskPipeline"; -import { SprintProgress } from "./SprintProgress"; -import { GoalProgress } from "./GoalProgress"; -import { NeedsAttention } from "./NeedsAttention"; import { RecoveryBanner } from "./RecoveryBanner"; import { BudgetAlertBanner } from "./BudgetAlertBanner"; @@ -54,194 +36,206 @@ export interface DashboardProps { projects: ProjectRecord[]; activity: ActivityRecord[]; costs: CostRecord[]; - connectors: ConnectorRecord[]; messages?: AgentMessageRecord[]; - browserActions?: BrowserActionRecord[]; - workflows?: WorkflowPipelineRecord[]; - meetings?: MeetingRecord[]; - sprints?: SprintRecord[]; onNavigate: (section: SectionId, entityId?: string) => void; } export function Dashboard(props: DashboardProps) { const t = useT(); - const { agents, tasks, runs, approvals, goals, projects, activity, costs, connectors, messages = [], browserActions = [], workflows = [], sprints = [], onNavigate } = props; + const { agents, tasks, runs, approvals, activity, costs, messages = [], onNavigate } = props; - const healthScore = useMemo( - () => computeHealthScore({ agents, tasks, runs, approvals, connectors, messages, browserActions, workflows }), - [agents, tasks, runs, approvals, connectors, messages, browserActions, workflows], - ); - - const liveRuns = useMemo(() => runs.filter((r) => r.status === "running" || r.status === "queued"), [runs]); - const failedRuns = useMemo(() => runs.filter((r) => r.status === "failed" || r.status === "timed_out" || r.status === "interrupted"), [runs]); - const pendingApprovals = useMemo(() => approvals.filter((a) => a.state === "pending"), [approvals]); - const activeTasks = useMemo(() => tasks.filter((t) => t.status === "in_progress" || t.status === "in_review"), [tasks]); - const activeGoals = useMemo(() => goals.filter((g) => g.status === "active"), [goals]); - const totalSpend = useMemo(() => costs.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0), [costs]); const activeAgents = useMemo(() => agents.filter((a) => a.status !== "terminated" && a.status !== "pending_approval"), [agents]); - const readyConnectors = useMemo(() => connectors.filter((connector) => isConnectorExecutionReady(connector)), [connectors]); - const activeSprint = useMemo(() => sprints.find((s) => s.status === "active"), [sprints]); - const unreadMessages = useMemo(() => messages.filter((message) => !message.readAt), [messages]); - const urgentMessages = useMemo(() => messages.filter((message) => !message.readAt && (message.priority === "urgent" || message.channel === "incident")), [messages]); - const blockedBrowserActions = useMemo(() => browserActions.filter((action) => action.status === "approval_required" || action.status === "failed"), [browserActions]); - const stalledWorkflows = useMemo(() => workflows.filter((workflow) => workflow.status === "paused" || workflow.status === "failed"), [workflows]); + const activeTasks = useMemo(() => tasks.filter((t) => t.status === "in_progress" || t.status === "in_review"), [tasks]); + const pendingApprovals = useMemo(() => approvals.filter((a) => a.state === "pending"), [approvals]); + const totalSpend = useMemo(() => costs.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0), [costs]); + + // Attention items + const failedRuns = useMemo(() => runs.filter((r) => r.status === "failed" || r.status === "timed_out" || r.status === "interrupted"), [runs]); + const urgentMessages = useMemo(() => messages.filter((m) => !m.readAt && (m.priority === "urgent" || m.channel === "incident")), [messages]); + const hasAttentionItems = pendingApprovals.length > 0 || failedRuns.length > 0 || urgentMessages.length > 0; + + // Active work — agents currently running tasks + const activeWork = useMemo(() => { + return runs + .filter((r) => r.status === "running" || r.status === "queued") + .map((run) => { + const agent = agents.find((a) => a.id === run.agentId); + const task = tasks.find((t) => t.id === run.taskId); + return { run, agent, task }; + }) + .slice(0, 8); + }, [runs, agents, tasks]); return (
- {/* Recovery banner for interrupted runs */} + {/* Conditional banners */} - - {/* Budget alert banner for agents approaching budget limits */} - {/* Health + Metrics */} -
-
- -
-
-
{activeAgents.length}
-
Agents
-
-
-
-
{readyConnectors.length}/{connectors.length}
-
Connectors
-
-
-
-
{activeGoals.length}
-
Goals
-
-
-
- -
- 0 ? "success" : "default"} onClick={() => onNavigate("runs")} subtitle={liveRuns.length > 0 ? `${liveRuns.filter((r) => r.status === "running").length} ${t("dash.running")}` : t("dash.allQuiet")} /> - 0 ? "accent" : "default"} onClick={() => onNavigate("tasks")} subtitle={`${tasks.filter((t) => t.status === "todo").length} ${t("dash.queued")}`} /> - 0 ? "warn" : "default"} onClick={() => onNavigate("approvals")} subtitle={pendingApprovals.length > 0 ? t("dash.needsAttention") : t("dash.allClear")} /> - 0 ? "danger" : "default"} onClick={() => onNavigate("runs")} /> - onNavigate("costs")} /> - p.status === "in_progress").length} icon={Target} onClick={() => onNavigate("projects")} subtitle={`${projects.length} ${t("dash.total")}`} /> - 0 ? "warn" : unreadMessages.length > 0 ? "accent" : "default"} onClick={() => onNavigate("communication")} subtitle={urgentMessages.length > 0 ? `${urgentMessages.length} urgent` : unreadMessages.length > 0 ? "Needs triage" : "All read"} /> - 0 ? "warn" : "default"} onClick={() => onNavigate("social")} subtitle={blockedBrowserActions.length > 0 ? "Blocked or failed" : "Nominal"} /> - 0 ? "warn" : "default"} onClick={() => onNavigate("automation")} subtitle={stalledWorkflows.length > 0 ? "Paused or failed" : "Healthy"} /> -
+ {/* Key Metrics — 4 cards */} +
+ 0 ? "accent" : "default"} + onClick={() => onNavigate("tasks")} + subtitle={`${tasks.filter((t) => t.status === "todo").length} ${t("dash.queued")}`} + /> + 0 ? "warn" : "default"} + onClick={() => onNavigate("approvals")} + subtitle={pendingApprovals.length > 0 ? t("dash.needsAttention") : t("dash.allClear")} + /> + a.status === "running").length > 0 ? "success" : "default"} + onClick={() => onNavigate("agents")} + subtitle={`${activeAgents.filter((a) => a.status === "running").length} running`} + /> + onNavigate("settings")} + />
- {/* Department Overview */} -
-
-
{t("dash.departments")}
- -
- -
- - {/* Sprint + Goals */} -
- {activeSprint ? ( -
-
-
{t("dash.currentSprint")}
- -
- -
- ) : null} + {/* Needs Attention — moved to top, only shows when items exist */} + {hasAttentionItems ? (
-
-
{t("dash.goalProgress")}
- +
+ {t("dash.needsAttentionSection")} +
+
+ {pendingApprovals.slice(0, 5).map((approval) => { + const requester = agents.find((a) => a.id === approval.requestedByAgentId); + return ( + + ); + })} + {failedRuns.slice(0, 3).map((run) => { + const agent = agents.find((a) => a.id === run.agentId); + return ( + + ); + })} + {urgentMessages.slice(0, 3).map((msg) => { + const sender = agents.find((a) => a.id === msg.fromAgentId); + return ( + + ); + })}
-
-
+ ) : null} - {/* Task Pipeline */} + {/* Active Work — what's running right now */}
-
{t("dash.taskPipeline")}
-
-
- -
-
- - {/* Workforce + Activity */} -
-
-
-
{t("dash.workforce")}
- -
- onNavigate("agents", id)} /> -
-
-
-
{t("dash.recentActivity")}
- -
-
- -
-
-
- - {/* Connector Health */} -
-
{t("dash.infrastructure")}
-
- {connectors.map((connector) => { - const isReady = isConnectorExecutionReady(connector); - const isError = connector.status === "error" || connector.status === "auth_required"; - const agentsUsing = agents.filter((a) => a.connectorId === connector.id).length; - return ( + {activeWork.length > 0 ? ( +
+ {activeWork.map(({ run, agent, task }) => ( - ); - })} -
+ ))} +
+ ) : ( +
+
{t("dash.allQuiet")}
+
No tasks currently running
+
+ )}
- {/* Needs Attention */} - + {/* Recent Activity */} +
+
+
+ {t("dash.recentActivity")} +
+
+
+ +
+
); }