refactor(hiring): delete three redundant sections (pending-panel + summary cards + department headcount)
HiringPipeline carried three panels that restated information already
shown — one with more emphasis — on the Team page or in the stage
filter directly above them:
1. "Pending approval" mini-Panel (lines 121-187): listed the same
candidates that the user reaches by clicking the `pending_approval`
chip in the stage filter at the top of the page. The mini-Panel
also carried its own Approve / Reject buttons, but those approvals
are resolvable from Approvals section too. Doubling the approval
path increases the chance of racing clicks between the two
surfaces. Deleted.
2. 4-card summary row at the bottom (lines 246-267): Total agents /
Pending hires / Departments / Monthly payroll. The first two are
rendered on the Team page by TeamHeaderSummary (commit 36bed1b).
Payroll can return via a Team-page dedicated surface when there
is a business reason; until then it is clutter. Deleted.
3. "Department headcount" Panel (lines 269-293): grid of active+
pending count per department. OrgChart already shows this through
the department colour on each agent card, and TeamHeaderSummary
covers the aggregate. Two places describing the same fact is one
too many. Deleted.
Also:
- `onRefresh` prop removed from HiringPipeline + OrganizationSections
caller — only the (now-deleted) approve/reject buttons used it.
- `Card`, `UserPlus`, `Check`, `unwrap` imports dropped because the
deletions made them unused.
- Unused `i` index in Dashboard.tsx activeWork map callback cleaned
up (left over from the previous commit that removed the
arbitrary animation gate).
File size: HiringPipeline.tsx 296 LOC → 175 LOC (net −121).
Typecheck + lint pass.
This commit is contained in:
1 parent
8b6826f6d8
commit
7c4d7a4a5e
3 files changed
+3
-125
No files matched your search
@@ -187,7 +187,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
</div>
|
||||
{activeWork.length > 0 ? (
|
||||
<div className="rounded-lg border border-border bg-card divide-y divide-[color:var(--border)]">
|
||||
{activeWork.map(({ run, agent, task }, i) => (
|
||||
{activeWork.map(({ run, agent, task }) => (
|
||||
<button
|
||||
key={run.id}
|
||||
type="button"
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
ArrowRight,
|
||||
Check,
|
||||
UserPlus,
|
||||
X,
|
||||
} from "../ui/icons";
|
||||
import type { AgentRecord, ApprovalRecord } from "@shared/types";
|
||||
import { useT } from "../../i18n";
|
||||
import { getAgentInitials } from "../../lib/formatters";
|
||||
import { unwrap } from "../../lib/desktop";
|
||||
import { safeJson } from "../../lib/safe-json";
|
||||
import { ActionButton, Card, EmptyState, Panel, StatusPill } from "../ui";
|
||||
import { ActionButton, EmptyState, Panel, StatusPill } from "../ui";
|
||||
import {
|
||||
type HiringStage,
|
||||
type HiringCandidate,
|
||||
@@ -22,11 +19,10 @@ import {
|
||||
interface HiringPipelineProps {
|
||||
agents: AgentRecord[];
|
||||
approvals: ApprovalRecord[];
|
||||
onRefresh: () => Promise<void>;
|
||||
onNavigateToAgent: (agentId: string) => void;
|
||||
}
|
||||
|
||||
export function HiringPipeline({ agents, approvals, onRefresh, onNavigateToAgent }: HiringPipelineProps) {
|
||||
export function HiringPipeline({ agents, approvals, onNavigateToAgent }: HiringPipelineProps) {
|
||||
const t = useT();
|
||||
const [filterStage, setFilterStage] = useState<HiringStage | "all">("all");
|
||||
|
||||
@@ -77,7 +73,6 @@ export function HiringPipeline({ agents, approvals, onRefresh, onNavigateToAgent
|
||||
return acc;
|
||||
}, {} as Record<HiringStage, number>);
|
||||
|
||||
const pendingCount = stageCounts.requisition + stageCounts.pending_approval;
|
||||
const activeHires = allCandidates.filter(c => c.stage !== "active" && c.stage !== "rejected").length;
|
||||
|
||||
return (
|
||||
@@ -118,74 +113,6 @@ export function HiringPipeline({ agents, approvals, onRefresh, onNavigateToAgent
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
{pendingCount > 0 && (
|
||||
<Panel title={t("hiring.pendingApproval")}>
|
||||
<div className="divide-y divide-[color:var(--border)]">
|
||||
{allCandidates
|
||||
.filter(c => c.stage === "requisition" || c.stage === "pending_approval")
|
||||
.map((candidate) => {
|
||||
const a = candidate.approval;
|
||||
const ag = candidate.agent;
|
||||
return (
|
||||
<div key={a?.id ?? ag?.id} className="px-4 py-3 flex items-start gap-3">
|
||||
<div className="flex-shrink-0 mt-0.5 h-8 w-8 rounded-full bg-warning-muted flex items-center justify-center">
|
||||
<UserPlus size={16} className="text-warning" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm text-foreground">{ag?.name ?? t("hiring.pendingHires")}</span>
|
||||
<span className={`text-[10px] px-1.5 py-0.5 rounded ${stageColor(candidate.stage)}`}>
|
||||
{stageLabelMap[candidate.stage]}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
{ag ? `${ag.role} — ${ag.title}` : a?.payloadSummary}
|
||||
</div>
|
||||
{ag?.capabilities && (
|
||||
<div className="text-xs text-muted-foreground mt-1 line-clamp-2">{ag.capabilities}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{a && a.state === "pending" && (
|
||||
<>
|
||||
<ActionButton
|
||||
label={t("hiring.approved")}
|
||||
icon={Check}
|
||||
tone="accent"
|
||||
onClick={async () => {
|
||||
unwrap(await window.agentCompany.decideApproval({
|
||||
companyId: a.companyId,
|
||||
approvalId: a.id,
|
||||
state: "approved",
|
||||
decisionNote: t("hiring.approvedNote"),
|
||||
}));
|
||||
await onRefresh();
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
label={t("status.rejected")}
|
||||
icon={X}
|
||||
tone="danger"
|
||||
onClick={async () => {
|
||||
unwrap(await window.agentCompany.decideApproval({
|
||||
companyId: a.companyId,
|
||||
approvalId: a.id,
|
||||
state: "rejected",
|
||||
decisionNote: t("hiring.rejectedNote"),
|
||||
}));
|
||||
await onRefresh();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Panel>
|
||||
)}
|
||||
|
||||
<Panel
|
||||
title={filterStage === "all" ? t("hiring.allAgents") : `${stageLabelMap[filterStage]} (${filteredCandidates.length})`}
|
||||
action={
|
||||
@@ -243,54 +170,6 @@ export function HiringPipeline({ agents, approvals, onRefresh, onNavigateToAgent
|
||||
)}
|
||||
</Panel>
|
||||
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<Card padding="sm">
|
||||
<div className="text-[10px] uppercase text-muted-foreground tracking-wider">{t("hiring.totalAgents")}</div>
|
||||
<div className="text-2xl font-bold text-foreground mt-1">{agents.filter(a => a.status !== "terminated").length}</div>
|
||||
</Card>
|
||||
<Card padding="sm">
|
||||
<div className="text-[10px] uppercase text-muted-foreground tracking-wider">{t("hiring.pendingHires")}</div>
|
||||
<div className="text-2xl font-bold text-warning mt-1">{agents.filter(a => a.status === "pending_approval").length}</div>
|
||||
</Card>
|
||||
<Card padding="sm">
|
||||
<div className="text-[10px] uppercase text-muted-foreground tracking-wider">{t("hiring.departments")}</div>
|
||||
<div className="text-2xl font-bold text-foreground mt-1">
|
||||
{new Set(agents.filter(a => a.department && a.status !== "terminated").map(a => a.department)).size}
|
||||
</div>
|
||||
</Card>
|
||||
<Card padding="sm">
|
||||
<div className="text-[10px] uppercase text-muted-foreground tracking-wider">{t("hiring.monthlyPayroll")}</div>
|
||||
<div className="text-2xl font-bold text-foreground mt-1">
|
||||
${agents.filter(a => a.status !== "terminated" && a.status !== "pending_approval").reduce((s, a) => s + a.budgetMonthlyUsd, 0).toLocaleString()}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Panel title={t("hiring.departmentHeadcount")}>
|
||||
<div className="grid grid-cols-3 gap-2 p-4">
|
||||
{(["executive", "engineering", "product", "design", "marketing", "sales", "hr", "finance", "legal", "customer_support", "operations", "research"] as const).map(dept => {
|
||||
const deptAgents = agents.filter(a => a.department === dept && a.status !== "terminated");
|
||||
const pending = deptAgents.filter(a => a.status === "pending_approval").length;
|
||||
const active = deptAgents.filter(a => a.status !== "pending_approval").length;
|
||||
if (active === 0 && pending === 0) return null;
|
||||
return (
|
||||
<div key={dept} className="bg-muted rounded-md px-3 py-2">
|
||||
<div className="text-xs font-medium text-foreground capitalize">{dept.replace("_", " ")}</div>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="text-sm font-bold text-foreground">{active}</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t("hiring.active")}</span>
|
||||
{pending > 0 && (
|
||||
<>
|
||||
<span className="text-sm font-bold text-warning">+{pending}</span>
|
||||
<span className="text-[10px] text-warning">{t("hiring.pendingApproval")}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -117,7 +117,6 @@ export function OrganizationSections({ section }: OrganizationSectionsProps) {
|
||||
<HiringPipeline
|
||||
agents={companyAgents}
|
||||
approvals={(snapshot?.approvals ?? []).filter(a => a.companyId === currentCompany.id)}
|
||||
onRefresh={refresh}
|
||||
onNavigateToAgent={(id) => { setSelectedAgentId(id); setSection("agents"); }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
Reference in new issue
Block a user