huskies: merge 715_refactor_decompose_frontend_src_components_workitemdetailpanel_tsx_827_lines
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
/** Header sub-component for WorkItemDetailPanel. */
|
||||
|
||||
import type { AgentConfigInfo, AgentInfo, AgentStatusValue } from "../api/agents";
|
||||
import { STAGE_LABELS, formatStoryTitle } from "./workItemDetailPanelUtils";
|
||||
|
||||
const STAGE_TO_AGENT_STAGE: Record<string, string> = {
|
||||
current: "coder",
|
||||
qa: "qa",
|
||||
merge: "mergemaster",
|
||||
};
|
||||
|
||||
interface WorkItemDetailPanelHeaderProps {
|
||||
storyId: string;
|
||||
name: string | null;
|
||||
stage: string;
|
||||
assignedAgent: string | null;
|
||||
agentConfig: AgentConfigInfo[];
|
||||
agentInfo: AgentInfo | null;
|
||||
agentStatus: AgentStatusValue | null;
|
||||
assigning: boolean;
|
||||
assignError: string | null;
|
||||
onAgentAssign: (agentName: string) => Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel header: title, stage label, agent assignment dropdown, and close button.
|
||||
*/
|
||||
export function WorkItemDetailPanelHeader({
|
||||
storyId,
|
||||
name,
|
||||
stage,
|
||||
assignedAgent,
|
||||
agentConfig,
|
||||
agentInfo,
|
||||
agentStatus,
|
||||
assigning,
|
||||
assignError,
|
||||
onAgentAssign,
|
||||
onClose,
|
||||
}: WorkItemDetailPanelHeaderProps) {
|
||||
const stageLabel = STAGE_LABELS[stage] ?? stage;
|
||||
const filteredAgents = agentConfig.filter(
|
||||
(a) => a.stage === STAGE_TO_AGENT_STAGE[stage],
|
||||
);
|
||||
const activeAgentName =
|
||||
agentInfo && (agentStatus === "running" || agentStatus === "pending")
|
||||
? agentInfo.agent_name
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "12px 16px",
|
||||
borderBottom: "1px solid #333",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "2px",
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-testid="detail-panel-title"
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.95em",
|
||||
color: "#ececec",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{formatStoryTitle(storyId, name)}
|
||||
</div>
|
||||
{stage && (
|
||||
<div
|
||||
data-testid="detail-panel-stage"
|
||||
style={{ fontSize: "0.75em", color: "#888" }}
|
||||
>
|
||||
{stageLabel}
|
||||
</div>
|
||||
)}
|
||||
{filteredAgents.length > 0 && (
|
||||
<div
|
||||
data-testid="detail-panel-agent-assignment"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
marginTop: "4px",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: "0.75em", color: "#666" }}>Agent:</span>
|
||||
<select
|
||||
data-testid="agent-assignment-dropdown"
|
||||
disabled={assigning}
|
||||
value={activeAgentName ?? assignedAgent ?? ""}
|
||||
onChange={(e) => onAgentAssign(e.target.value)}
|
||||
style={{
|
||||
background: "#1a1a1a",
|
||||
border: "1px solid #444",
|
||||
borderRadius: "4px",
|
||||
color: "#ccc",
|
||||
cursor: assigning ? "not-allowed" : "pointer",
|
||||
fontSize: "0.75em",
|
||||
padding: "2px 6px",
|
||||
opacity: assigning ? 0.6 : 1,
|
||||
}}
|
||||
>
|
||||
<option value="">— none —</option>
|
||||
{filteredAgents.map((a) => {
|
||||
const isRunning =
|
||||
agentInfo?.agent_name === a.name &&
|
||||
agentStatus === "running";
|
||||
const isPending =
|
||||
agentInfo?.agent_name === a.name &&
|
||||
agentStatus === "pending";
|
||||
const statusLabel = isRunning
|
||||
? " — running"
|
||||
: isPending
|
||||
? " — pending"
|
||||
: " — idle";
|
||||
const modelPart = a.model ? ` (${a.model})` : "";
|
||||
return (
|
||||
<option key={a.name} value={a.name}>
|
||||
{a.name}
|
||||
{modelPart}
|
||||
{statusLabel}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
{assigning && (
|
||||
<span style={{ fontSize: "0.7em", color: "#888" }}>
|
||||
Assigning…
|
||||
</span>
|
||||
)}
|
||||
{assignError && (
|
||||
<span
|
||||
data-testid="agent-assignment-error"
|
||||
style={{ fontSize: "0.7em", color: "#f85149" }}
|
||||
>
|
||||
{assignError}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{filteredAgents.length === 0 && assignedAgent ? (
|
||||
<div
|
||||
data-testid="detail-panel-assigned-agent"
|
||||
style={{ fontSize: "0.75em", color: "#888" }}
|
||||
>
|
||||
Agent: {assignedAgent}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="detail-panel-close"
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: "none",
|
||||
border: "1px solid #444",
|
||||
borderRadius: "6px",
|
||||
color: "#aaa",
|
||||
cursor: "pointer",
|
||||
padding: "4px 10px",
|
||||
fontSize: "0.8em",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user