diff --git a/src/main/automation/automation-approval-handler.ts b/src/main/automation/automation-approval-handler.ts index 23e7a70d..2533501a 100644 --- a/src/main/automation/automation-approval-handler.ts +++ b/src/main/automation/automation-approval-handler.ts @@ -123,7 +123,7 @@ export function handleApprovalResolved( title: localizeNotification(hireLocale, "Agent Hired", "新员工入职"), body: localizeNotification(hireLocale, `${agent.name} joined as ${agent.role}`, `${agent.name} 已加入,职位:${agent.role}`), urgency: "informational", - navigation: { section: "orgchart", entityId: agent.id }, + navigation: { section: "agents", entityId: agent.id }, batchKey: "agent_hired", }); diff --git a/src/renderer/components/organization/TeamHeaderSummary.tsx b/src/renderer/components/organization/TeamHeaderSummary.tsx new file mode 100644 index 00000000..248403c6 --- /dev/null +++ b/src/renderer/components/organization/TeamHeaderSummary.tsx @@ -0,0 +1,81 @@ +import type { AgentRecord, ApprovalRecord } from "@shared/types"; +import { formatMoney } from "../../lib/formatters"; +import { useT } from "../../i18n"; + +interface TeamHeaderSummaryProps { + agents: AgentRecord[]; + pendingApprovals: ApprovalRecord[]; +} + +/** + * Compact summary strip above the org chart. Answers the "what's going on + * with the team right now?" question without requiring the user to scan + * the chart + roster + approvals pages separately. + * + * Shows: total members, how many are actively running, how many need + * attention (paused/error/pending_approval), pending hire approvals, + * and aggregate monthly budget burn. Computed from existing snapshot + * data — no extra IPC round-trip. + */ +export function TeamHeaderSummary({ agents, pendingApprovals }: TeamHeaderSummaryProps) { + const t = useT(); + + const active = agents.filter((a) => a.status !== "terminated"); + const running = active.filter((a) => a.status === "running").length; + const attention = active.filter( + (a) => a.status === "paused" || a.status === "error" || a.status === "pending_approval", + ).length; + const pendingHires = pendingApprovals.filter((a) => a.type === "hire_agent" && a.state === "pending").length; + + const totalBudget = active.reduce((sum, a) => sum + (a.budgetMonthlyUsd ?? 0), 0); + const totalSpent = active.reduce((sum, a) => sum + (a.spentMonthlyUsd ?? 0), 0); + const budgetPct = totalBudget > 0 ? Math.round((totalSpent / totalBudget) * 100) : 0; + + const items: Array<{ label: string; value: string; emphasis?: "warn" | "muted" }> = [ + { label: t("team.summary.members"), value: String(active.length) }, + { label: t("team.summary.running"), value: String(running) }, + { + label: t("team.summary.attention"), + value: String(attention), + emphasis: attention > 0 ? "warn" : "muted", + }, + { + label: t("team.summary.pendingHires"), + value: String(pendingHires), + emphasis: pendingHires > 0 ? "warn" : "muted", + }, + { + label: t("team.summary.budget"), + value: + totalBudget > 0 + ? t("team.summary.budgetFormat") + .replace("{spent}", formatMoney(totalSpent)) + .replace("{budget}", formatMoney(totalBudget)) + .replace("{pct}", String(budgetPct)) + : formatMoney(totalSpent), + }, + ]; + + return ( +
+ {items.map((item) => ( +
+ + {item.label} + + + {item.value} + +
+ ))} +
+ ); +} diff --git a/src/renderer/i18n/en.ts b/src/renderer/i18n/en.ts index 0c8ba279..d2d40cf0 100644 --- a/src/renderer/i18n/en.ts +++ b/src/renderer/i18n/en.ts @@ -6,7 +6,6 @@ export const en: Record = { "nav.performance": "Performance", "nav.runs": "Runs", "nav.agents": "Team", - "nav.orgchart": "Org Chart", "nav.hiring": "Hiring", "nav.communication": "Messages", "nav.goals": "Goals", @@ -340,6 +339,12 @@ export const en: Record = { // ── Organization page ── "org.agentRoster": "Team members", + "team.summary.members": "Members", + "team.summary.running": "Running", + "team.summary.attention": "Attention", + "team.summary.pendingHires": "Pending hires", + "team.summary.budget": "This month", + "team.summary.budgetFormat": "{spent} / {budget} · {pct}%", "org.newHireRequest": "New hire request", "org.editAgent": "Edit", "org.editAgentTitle": "Edit {name} ({role})", diff --git a/src/renderer/i18n/zh.ts b/src/renderer/i18n/zh.ts index 14e02d70..9c13e15c 100644 --- a/src/renderer/i18n/zh.ts +++ b/src/renderer/i18n/zh.ts @@ -6,7 +6,6 @@ export const zh: Record = { "nav.performance": "绩效", "nav.runs": "运行", "nav.agents": "团队", - "nav.orgchart": "组织架构", "nav.hiring": "招聘", "nav.communication": "消息", "nav.goals": "目标", @@ -340,6 +339,12 @@ export const zh: Record = { // ── 组织页面 ── "org.agentRoster": "团队成员", + "team.summary.members": "成员", + "team.summary.running": "运行中", + "team.summary.attention": "需关注", + "team.summary.pendingHires": "待审招聘", + "team.summary.budget": "本月预算", + "team.summary.budgetFormat": "{spent} / {budget} · {pct}%", "org.newHireRequest": "新招聘申请", "org.editAgent": "编辑", "org.editAgentTitle": "编辑 {name}({role})", diff --git a/src/renderer/views/sections/OrganizationSections.tsx b/src/renderer/views/sections/OrganizationSections.tsx index dd6cf631..6f136747 100644 --- a/src/renderer/views/sections/OrganizationSections.tsx +++ b/src/renderer/views/sections/OrganizationSections.tsx @@ -12,6 +12,7 @@ import { GoalCreator } from "../../components/organization/GoalCreator"; import { GoalEditor } from "../../components/organization/GoalEditor"; import { GoalList } from "../../components/organization/GoalList"; import { OrgChart } from "../../components/organization/OrgChart"; +import { TeamHeaderSummary } from "../../components/organization/TeamHeaderSummary"; import { ProjectCreator } from "../../components/organization/ProjectCreator"; import { ProjectEditor } from "../../components/organization/ProjectEditor"; import { ProjectList } from "../../components/organization/ProjectList"; @@ -35,6 +36,7 @@ export function OrganizationSections({ section }: OrganizationSectionsProps) { companyGoals, companyProjects, companyWorkspaces, + pendingApprovals, editingAgent, selectedGoal, selectedProject, @@ -65,6 +67,7 @@ export function OrganizationSections({ section }: OrganizationSectionsProps) { <> {section === "agents" ? ( <> + setEditingAgentId(NEW_ENTITY_ID)} />}> ) : null} - {section === "orgchart" ? ( - { setSelectedAgentId(id); setSection("agents"); }} /> - ) : null} - {section === "hiring" && currentCompany ? ( { title: "Agent Hired", body: "Alice joined", urgency: "informational", - navigation: { section: "orgchart", entityId: "agent-1" }, + navigation: { section: "agents", entityId: "agent-1" }, batchKey: "agent_hired", }); @@ -192,7 +192,7 @@ describe("notification-service", () => { title: "Agent Hired", body: "Bob joined", urgency: "informational", - navigation: { section: "orgchart", entityId: "agent-2" }, + navigation: { section: "agents", entityId: "agent-2" }, batchKey: "agent_hired", });