Files
next-tjwater-frontend/src/features/workbench/components/scheduled-condition-feed-utils.ts
T
jiang f4318a9b9c feat: refine workbench visuals and map controls
Unify the Agent history extension with the header acrylic surface, preserve the full conversation body, and consolidate shared control and status styling.

Restore map flow and SCADA controller behavior, remove obsolete rendering paths, and extend regression coverage. Button press coverage now releases outside the target so state assertions cannot accidentally toggle the control.
2026-07-28 16:39:36 +08:00

200 lines
5.5 KiB
TypeScript

import {
AlertTriangle,
CheckCircle2,
Clock3,
Loader2,
XCircle
} from "lucide-react";
import type { StatusActivity, StatusTone } from "@/shared/ui/status";
import type {
ScheduledConditionItem,
ScheduledConditionItemStatus,
ScheduledWorkOrderItem,
ScheduledWorkOrderStage
} from "../types";
export const CONDITION_FILTER_ALL = "__all__";
export const statusLabels: Record<ScheduledConditionItemStatus, string> = {
completed: "完成",
running: "执行中",
awaiting_reply: "待复令",
warning: "关注",
error: "异常",
pending: "预约"
};
const statusTones = {
completed: "success",
running: "info",
awaiting_reply: "warning",
warning: "warning",
error: "danger",
pending: "neutral"
} satisfies Record<ScheduledConditionItemStatus, StatusTone>;
const statusActivities = {
completed: "static",
running: "loading",
awaiting_reply: "static",
warning: "static",
error: "static",
pending: "static"
} satisfies Record<ScheduledConditionItemStatus, StatusActivity>;
const statusIcons: Record<ScheduledConditionItemStatus, typeof CheckCircle2> = {
completed: CheckCircle2,
running: Loader2,
awaiting_reply: Clock3,
warning: AlertTriangle,
error: XCircle,
pending: Clock3
};
const workOrderStageTones = {
dispatch: "neutral",
execution: "info",
reply: "warning"
} satisfies Record<ScheduledWorkOrderStage["id"], StatusTone>;
const workOrderStageActivities = {
dispatch: "static",
execution: "loading",
reply: "static"
} satisfies Record<ScheduledWorkOrderStage["id"], StatusActivity>;
const conditionStatusOrder: ScheduledConditionItemStatus[] = [
"running",
"awaiting_reply",
"warning",
"error",
"completed",
"pending"
];
export type ConditionTaskFilterValue = typeof CONDITION_FILTER_ALL | string;
export type ConditionStatusFilterValue = typeof CONDITION_FILTER_ALL | ScheduledConditionItemStatus;
export type ConditionTaskFilterOption = {
value: ConditionTaskFilterValue;
label: string;
count: number;
};
export type ConditionStatusFilterOption = {
value: ScheduledConditionItemStatus;
label: string;
count: number;
};
export function groupScheduledConditions(conditions: ScheduledConditionItem[]) {
const recentConditions = conditions
.filter((condition) => condition.kind === "condition")
.sort((a, b) => getConditionTime(b) - getConditionTime(a));
const futureWorkOrders = conditions
.filter((condition) => condition.kind === "work_order")
.sort((a, b) => getConditionTime(a) - getConditionTime(b));
return { recentConditions, futureWorkOrders };
}
export function createTaskFilterOptions(conditions: ScheduledConditionItem[]): ConditionTaskFilterOption[] {
const taskCounts = new Map<string, number>();
conditions.forEach((condition) => {
taskCounts.set(condition.title, (taskCounts.get(condition.title) ?? 0) + 1);
});
return [...taskCounts.entries()]
.map(([label, count]) => ({ value: label, label, count }))
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label, "zh-CN"));
}
export function createStatusFilterOptions(conditions: ScheduledConditionItem[]): ConditionStatusFilterOption[] {
const statusCounts = new Map<ScheduledConditionItemStatus, number>();
conditions.forEach((condition) => {
statusCounts.set(condition.status, (statusCounts.get(condition.status) ?? 0) + 1);
});
return conditionStatusOrder.map((status) => ({
value: status,
label: statusLabels[status],
count: statusCounts.get(status) ?? 0
}));
}
export function getConditionTime(condition: ScheduledConditionItem) {
return Date.parse(condition.scheduledAt);
}
export function formatTime(value: string) {
const match = value.match(/T(\d{2}:\d{2})/);
return match?.[1] ?? value;
}
export function formatTimeRange(value: string, durationMinutes: number | undefined) {
const startTime = formatTime(value);
if (typeof durationMinutes !== "number") {
return startTime;
}
const endDate = new Date(Date.parse(value) + durationMinutes * 60_000);
const endTime = `${endDate.getHours().toString().padStart(2, "0")}:${endDate.getMinutes().toString().padStart(2, "0")}`;
return `${startTime} - ${endTime}`;
}
export function formatDuration(durationMinutes: number | undefined, status: ScheduledConditionItemStatus) {
if (status === "running") {
return "执行中";
}
if (typeof durationMinutes !== "number") {
return "未记录";
}
return `${durationMinutes} 分钟`;
}
export function getRiskLabel(riskLevel: ScheduledConditionItem["riskLevel"]) {
if (riskLevel === "critical") {
return "高风险";
}
if (riskLevel === "attention") {
return "关注";
}
return "正常";
}
export function getWorkOrderCurrentStage(condition: ScheduledWorkOrderItem) {
return (
condition.stages.find((stage) => stage.status === "current") ??
condition.stages.find((stage) => stage.status === "pending") ??
condition.stages.at(-1) ??
null
);
}
export function getWorkOrderStageTone(stageId?: ScheduledWorkOrderStage["id"]) {
return stageId ? workOrderStageTones[stageId] : "neutral";
}
export function getWorkOrderStageActivity(stageId?: ScheduledWorkOrderStage["id"]) {
return stageId ? workOrderStageActivities[stageId] : "static";
}
export function getStatusIcon(status: ScheduledConditionItemStatus) {
return statusIcons[status];
}
export function getStatusTone(status: ScheduledConditionItemStatus) {
return statusTones[status];
}
export function getStatusActivity(status: ScheduledConditionItemStatus) {
return statusActivities[status];
}