feat(awooop): surface commander inserted work items
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 53s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 53s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -9157,6 +9157,30 @@
|
||||
"evidenceBoundary": "Evidence boundary"
|
||||
}
|
||||
},
|
||||
"commanderInsertedRequirements": {
|
||||
"eyebrow": "Mainline priority",
|
||||
"title": "Commander inserted requirement work items",
|
||||
"subtitle": "Turns the requirements inserted during this run into ordered work items with P0/P1/P2/P3 priority, status, acceptance criteria, and next action.",
|
||||
"total": "Total work items",
|
||||
"next": "Current priority",
|
||||
"nextAction": "Next action",
|
||||
"acceptance": "Acceptance",
|
||||
"rowNextAction": "Next action",
|
||||
"loading": "Loading inserted requirement work items",
|
||||
"empty": "No inserted requirement work items read back yet.",
|
||||
"order": "order={order}",
|
||||
"source": "source={source}",
|
||||
"metrics": {
|
||||
"active": "active"
|
||||
},
|
||||
"statuses": {
|
||||
"done": "Done",
|
||||
"inProgress": "In progress",
|
||||
"pending": "Not started",
|
||||
"blocked": "Blocked",
|
||||
"deferred": "Deferred"
|
||||
}
|
||||
},
|
||||
"operatorSop": {
|
||||
"eyebrow": "操作 SOP 判讀",
|
||||
"title": "AI 受控卡點與自動化缺口接手面板",
|
||||
|
||||
@@ -9157,6 +9157,30 @@
|
||||
"evidenceBoundary": "證據邊界"
|
||||
}
|
||||
},
|
||||
"commanderInsertedRequirements": {
|
||||
"eyebrow": "主線優先序",
|
||||
"title": "統帥插入需求工作項",
|
||||
"subtitle": "把本輪中途插入的要求收成正式工作項,依 P0/P1/P2/P3 排序,並顯示狀態、驗收條件與下一步。",
|
||||
"total": "總工作項",
|
||||
"next": "目前優先項",
|
||||
"nextAction": "下一步",
|
||||
"acceptance": "驗收條件",
|
||||
"rowNextAction": "下一步",
|
||||
"loading": "讀取插入需求工作項",
|
||||
"empty": "尚未讀回插入需求工作項。",
|
||||
"order": "order={order}",
|
||||
"source": "source={source}",
|
||||
"metrics": {
|
||||
"active": "active"
|
||||
},
|
||||
"statuses": {
|
||||
"done": "已完成",
|
||||
"inProgress": "進行中",
|
||||
"pending": "未開始",
|
||||
"blocked": "阻塞",
|
||||
"deferred": "延後"
|
||||
}
|
||||
},
|
||||
"operatorSop": {
|
||||
"eyebrow": "操作 SOP 判讀",
|
||||
"title": "AI 受控卡點與自動化缺口接手面板",
|
||||
|
||||
@@ -1026,8 +1026,38 @@ type AiLoopLogSourceContract = {
|
||||
purpose?: string | null;
|
||||
};
|
||||
|
||||
type CommanderInsertedRequirementWorkItem = {
|
||||
id?: string | null;
|
||||
priority?: "P0" | "P1" | "P2" | "P3" | string | null;
|
||||
order?: number | null;
|
||||
status?: "done" | "in_progress" | "pending" | "blocked" | "deferred" | string | null;
|
||||
lane?: string | null;
|
||||
request?: string | null;
|
||||
normalized_work_item?: string | null;
|
||||
current_state?: string | null;
|
||||
acceptance?: string | null;
|
||||
next_action?: string | null;
|
||||
mapped_workplan_id?: string | null;
|
||||
visible_surface?: string | null;
|
||||
source?: string | null;
|
||||
};
|
||||
|
||||
type PriorityWorkOrderResponse = {
|
||||
summary?: {
|
||||
commander_inserted_requirement_work_item_count?: number | null;
|
||||
commander_inserted_requirement_p0_count?: number | null;
|
||||
commander_inserted_requirement_p1_count?: number | null;
|
||||
commander_inserted_requirement_p2_count?: number | null;
|
||||
commander_inserted_requirement_p3_count?: number | null;
|
||||
commander_inserted_requirement_done_count?: number | null;
|
||||
commander_inserted_requirement_in_progress_count?: number | null;
|
||||
commander_inserted_requirement_pending_count?: number | null;
|
||||
commander_inserted_requirement_blocked_count?: number | null;
|
||||
commander_inserted_requirement_deferred_count?: number | null;
|
||||
commander_inserted_requirement_next_id?: string | null;
|
||||
commander_inserted_requirement_next_priority?: string | null;
|
||||
commander_inserted_requirement_next_action?: string | null;
|
||||
commander_inserted_requirement_source?: string | null;
|
||||
ai_loop_current_blocker_id?: string | null;
|
||||
ai_loop_current_blocker_log_source_tag_count?: number | null;
|
||||
ai_loop_current_blocker_log_source_tag_keys?: string[] | null;
|
||||
@@ -1102,6 +1132,7 @@ type PriorityWorkOrderResponse = {
|
||||
controlled_cd_lane_live_metric_blockers?: string[] | null;
|
||||
} | null;
|
||||
}>;
|
||||
commander_inserted_requirement_work_items?: CommanderInsertedRequirementWorkItem[];
|
||||
};
|
||||
|
||||
type Telemetry = {
|
||||
@@ -8400,6 +8431,235 @@ function AiLoopLogSourceTagsPanel({
|
||||
);
|
||||
}
|
||||
|
||||
function commanderPriorityTone(priority: string) {
|
||||
switch (priority) {
|
||||
case "P0":
|
||||
return "border-[#f0c6a8] bg-[#fff8f1] text-[#9a4d16]";
|
||||
case "P1":
|
||||
return "border-[#c9d8ea] bg-[#eef5ff] text-[#1f5b9b]";
|
||||
case "P2":
|
||||
return "border-[#cbd7bf] bg-[#f4faef] text-[#3d6b24]";
|
||||
default:
|
||||
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
|
||||
}
|
||||
}
|
||||
|
||||
function commanderStatusTone(status: string) {
|
||||
switch (status) {
|
||||
case "done":
|
||||
return "border-[#b9d9c2] bg-[#f2fbf3] text-[#236332]";
|
||||
case "in_progress":
|
||||
return "border-[#c9d8ea] bg-[#eef5ff] text-[#1f5b9b]";
|
||||
case "blocked":
|
||||
return "border-[#f0c6a8] bg-[#fff8f1] text-[#9a4d16]";
|
||||
case "deferred":
|
||||
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
|
||||
default:
|
||||
return "border-[#e0ddd4] bg-white text-[#5f5b52]";
|
||||
}
|
||||
}
|
||||
|
||||
function CommanderInsertedRequirementsPanel({
|
||||
priority,
|
||||
loading,
|
||||
}: {
|
||||
priority: PriorityWorkOrderResponse | null;
|
||||
loading: boolean;
|
||||
}) {
|
||||
const t = useTranslations("awooop.workItems.commanderInsertedRequirements");
|
||||
const summary = priority?.summary;
|
||||
const items = (priority?.commander_inserted_requirement_work_items ?? [])
|
||||
.slice()
|
||||
.sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
|
||||
const total =
|
||||
summary?.commander_inserted_requirement_work_item_count ?? items.length;
|
||||
const nextId = summary?.commander_inserted_requirement_next_id ?? items[0]?.id ?? "--";
|
||||
const nextPriority =
|
||||
summary?.commander_inserted_requirement_next_priority ??
|
||||
items[0]?.priority ??
|
||||
"--";
|
||||
const nextAction =
|
||||
summary?.commander_inserted_requirement_next_action ??
|
||||
items[0]?.next_action ??
|
||||
"--";
|
||||
const source = summary?.commander_inserted_requirement_source ?? items[0]?.source ?? "--";
|
||||
const metrics = [
|
||||
{
|
||||
key: "p0",
|
||||
label: "P0",
|
||||
value: summary?.commander_inserted_requirement_p0_count ?? 0,
|
||||
tone: commanderPriorityTone("P0"),
|
||||
},
|
||||
{
|
||||
key: "p1",
|
||||
label: "P1",
|
||||
value: summary?.commander_inserted_requirement_p1_count ?? 0,
|
||||
tone: commanderPriorityTone("P1"),
|
||||
},
|
||||
{
|
||||
key: "p2",
|
||||
label: "P2",
|
||||
value: summary?.commander_inserted_requirement_p2_count ?? 0,
|
||||
tone: commanderPriorityTone("P2"),
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
label: t("metrics.active"),
|
||||
value:
|
||||
(summary?.commander_inserted_requirement_in_progress_count ?? 0) +
|
||||
(summary?.commander_inserted_requirement_pending_count ?? 0) +
|
||||
(summary?.commander_inserted_requirement_blocked_count ?? 0),
|
||||
tone: "border-[#e0ddd4] bg-white text-[#141413]",
|
||||
},
|
||||
];
|
||||
const statusLabels: Record<string, string> = {
|
||||
done: t("statuses.done"),
|
||||
in_progress: t("statuses.inProgress"),
|
||||
pending: t("statuses.pending"),
|
||||
blocked: t("statuses.blocked"),
|
||||
deferred: t("statuses.deferred"),
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
className="border border-[#e0ddd4] bg-white"
|
||||
data-testid="commander-inserted-requirement-work-items"
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<ListChecks className="h-5 w-5 shrink-0 text-[#d97757]" aria-hidden="true" />
|
||||
<div className="min-w-0">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
|
||||
{t("eyebrow")}
|
||||
</p>
|
||||
<h3 className="mt-1 text-base font-semibold tracking-normal text-[#141413]">
|
||||
{t("title")}
|
||||
</h3>
|
||||
<p className="mt-2 max-w-3xl text-xs leading-5 text-[#77736a]">
|
||||
{t("subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2 text-right">
|
||||
<div className="text-[11px] font-semibold text-[#77736a]">
|
||||
{t("total")}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-xl font-semibold text-[#141413]">
|
||||
{loading ? "--" : total}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{metrics.map((metric) => (
|
||||
<div key={metric.key} className={cn("border px-3 py-2", metric.tone)}>
|
||||
<div className="text-[11px] font-semibold">{metric.label}</div>
|
||||
<div className="mt-1 font-mono text-lg font-semibold text-[#141413]">
|
||||
{loading ? "--" : metric.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 grid gap-2 border border-[#c9d8ea] bg-[#eef5ff] p-3 md:grid-cols-[minmax(0,0.85fr)_minmax(0,2fr)]">
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-semibold text-[#1f5b9b]">
|
||||
{t("next")}
|
||||
</div>
|
||||
<div className="mt-1 break-words font-mono text-sm font-semibold text-[#141413]">
|
||||
{loading ? "--" : `${nextPriority}:${nextId}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-semibold text-[#1f5b9b]">
|
||||
{t("nextAction")}
|
||||
</div>
|
||||
<div className="mt-1 break-words text-xs leading-5 text-[#141413]">
|
||||
{loading ? "--" : nextAction}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2">
|
||||
{items.length > 0 ? (
|
||||
items.map((item) => {
|
||||
const itemPriority = String(item.priority ?? "--");
|
||||
const itemStatus = String(item.status ?? "pending");
|
||||
return (
|
||||
<div
|
||||
key={item.id ?? item.order}
|
||||
className="grid gap-3 border border-[#e0ddd4] bg-[#faf9f3] p-3 lg:grid-cols-[minmax(112px,0.45fr)_minmax(0,1.6fr)_minmax(0,1.1fr)]"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<span
|
||||
className={cn(
|
||||
"border px-2 py-0.5 font-mono text-[11px] font-semibold",
|
||||
commanderPriorityTone(itemPriority)
|
||||
)}
|
||||
>
|
||||
{itemPriority}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"border px-2 py-0.5 text-[11px] font-semibold",
|
||||
commanderStatusTone(itemStatus)
|
||||
)}
|
||||
>
|
||||
{statusLabels[itemStatus] ?? itemStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 break-all font-mono text-xs font-semibold text-[#141413]">
|
||||
{loading ? "--" : item.id}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-[10px] text-[#77736a]">
|
||||
{t("order", { order: item.order ?? 0 })}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="break-words text-xs font-semibold leading-5 text-[#141413]">
|
||||
{loading ? "--" : item.normalized_work_item}
|
||||
</div>
|
||||
<div className="mt-2 break-words text-[11px] leading-5 text-[#77736a]">
|
||||
{loading ? "--" : item.request}
|
||||
</div>
|
||||
<div className="mt-2 break-words font-mono text-[10px] leading-4 text-[#5f5b52]">
|
||||
{loading ? "--" : item.mapped_workplan_id}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 border border-[#d8d3c7] bg-white px-3 py-2">
|
||||
<div className="text-[11px] font-semibold text-[#77736a]">
|
||||
{t("acceptance")}
|
||||
</div>
|
||||
<div className="mt-1 break-words text-[11px] leading-5 text-[#141413]">
|
||||
{loading ? "--" : item.acceptance}
|
||||
</div>
|
||||
<div className="mt-2 text-[11px] font-semibold text-[#77736a]">
|
||||
{t("rowNextAction")}
|
||||
</div>
|
||||
<div className="mt-1 break-words text-[11px] leading-5 text-[#141413]">
|
||||
{loading ? "--" : item.next_action}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-6 text-center text-xs text-[#77736a]">
|
||||
{loading ? t("loading") : t("empty")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 break-words font-mono text-[10px] leading-4 text-[#9a968c]">
|
||||
{t("source", { source })}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AwoooPWorkItemsPage() {
|
||||
const t = useTranslations("awooop.workItems");
|
||||
const locale = useLocale();
|
||||
@@ -8687,6 +8947,11 @@ export default function AwoooPWorkItemsPage() {
|
||||
loading={loading && !telemetry.priorityWorkOrder}
|
||||
/>
|
||||
|
||||
<CommanderInsertedRequirementsPanel
|
||||
priority={telemetry.priorityWorkOrder}
|
||||
loading={loading && !telemetry.priorityWorkOrder}
|
||||
/>
|
||||
|
||||
<OperatorSopRail
|
||||
telemetry={telemetry}
|
||||
workItems={workItems}
|
||||
|
||||
Reference in New Issue
Block a user