feat(workbench): integrate operational workflows and map state
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
CalendarClock,
|
||||
CalendarPlus,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
ClipboardList,
|
||||
@@ -17,6 +18,8 @@ import type {
|
||||
WorkbenchWorkOrderDataSource
|
||||
} from "../types";
|
||||
import { createConditionConversationPrompt } from "../utils/scheduled-condition-prompts";
|
||||
import type { OperationalTaskDecision } from "../api/operational-task-client";
|
||||
import type { OperationalTaskDataSnapshot } from "../api/operational-task-client";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -26,7 +29,18 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from "@/shared/ui/dropdown-menu";
|
||||
import { StatusIcon as SemanticStatusIcon } from "@/shared/ui/status";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import { Textarea } from "@/shared/ui/textarea";
|
||||
import { DateTimeField } from "@/shared/ui/date-time-field";
|
||||
import { FieldSelect } from "@/shared/ui/field-select";
|
||||
import { ConditionDetailPanel, StatusPill } from "./scheduled-condition-detail-panel";
|
||||
import {
|
||||
WORKBENCH_TASK_CATEGORIES,
|
||||
getWorkbenchTaskCategory,
|
||||
groupConditionsByTaskCategory,
|
||||
type WorkbenchTaskCategory
|
||||
} from "./task-category";
|
||||
import {
|
||||
CONDITION_FILTER_ALL,
|
||||
createStatusFilterOptions,
|
||||
@@ -56,12 +70,22 @@ export type ScheduledConditionFeedPresentation =
|
||||
| "desktop-floating"
|
||||
| "mobile-sheet";
|
||||
|
||||
export type ScheduleCreateRequest = {
|
||||
scheduleKey: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
intervalSeconds: number;
|
||||
nextRunAt: string;
|
||||
};
|
||||
|
||||
type ScheduledConditionFeedProps = {
|
||||
presentation?: ScheduledConditionFeedPresentation;
|
||||
conditions?: ScheduledConditionItem[];
|
||||
expanded?: boolean;
|
||||
focusRequest?: ScheduledConditionFeedFocusRequest | null;
|
||||
loading?: boolean;
|
||||
backendLoading?: boolean;
|
||||
backendError?: string | null;
|
||||
dataSource?: WorkbenchRuntimeDataSource;
|
||||
workOrderDataSource?: WorkbenchWorkOrderDataSource;
|
||||
visible?: boolean;
|
||||
@@ -73,6 +97,13 @@ type ScheduledConditionFeedProps = {
|
||||
onClose?: () => void;
|
||||
onLoadHistorySession: (sessionId: string) => void | Promise<void>;
|
||||
onSubmitPrompt: (prompt: string) => void | Promise<void>;
|
||||
onOpenTaskCollaboration?: (taskId: string) => Promise<void>;
|
||||
onTaskDecision?: (taskId: string, actionId: string, expectedVersion: number, decision: OperationalTaskDecision, comment?: string) => Promise<unknown>;
|
||||
onTaskExecute?: (taskId: string, actionId: string, expectedVersion: number) => Promise<unknown>;
|
||||
onTaskReconcile?: (taskId: string, actionId: string, expectedVersion: number) => Promise<unknown>;
|
||||
onLoadTaskEvidenceSnapshot?: (taskId: string, evidenceId: string) => Promise<OperationalTaskDataSnapshot>;
|
||||
onTaskRefresh?: () => void;
|
||||
onCreateSchedule?: (input: ScheduleCreateRequest) => Promise<unknown>;
|
||||
};
|
||||
|
||||
type ScheduledConditionFeedFocusRequest = {
|
||||
@@ -86,6 +117,8 @@ export function ScheduledConditionFeed({
|
||||
expanded: controlledExpanded,
|
||||
focusRequest,
|
||||
loading = false,
|
||||
backendLoading = false,
|
||||
backendError = null,
|
||||
dataSource = "mock",
|
||||
workOrderDataSource = "mock",
|
||||
visible = true,
|
||||
@@ -96,7 +129,14 @@ export function ScheduledConditionFeed({
|
||||
onExpandAgent,
|
||||
onClose,
|
||||
onLoadHistorySession,
|
||||
onSubmitPrompt
|
||||
onSubmitPrompt,
|
||||
onOpenTaskCollaboration,
|
||||
onTaskDecision,
|
||||
onTaskExecute,
|
||||
onTaskReconcile,
|
||||
onLoadTaskEvidenceSnapshot,
|
||||
onTaskRefresh,
|
||||
onCreateSchedule
|
||||
}: ScheduledConditionFeedProps) {
|
||||
const [uncontrolledExpanded, setUncontrolledExpanded] = useState(false);
|
||||
const [uncontrolledSelectedConditionId, setUncontrolledSelectedConditionId] = useState<
|
||||
@@ -105,27 +145,46 @@ export function ScheduledConditionFeed({
|
||||
const [taskFilter, setTaskFilter] = useState<ConditionTaskFilterValue>(CONDITION_FILTER_ALL);
|
||||
const [statusFilter, setStatusFilter] =
|
||||
useState<ConditionStatusFilterValue>(CONDITION_FILTER_ALL);
|
||||
const [taskCategory, setTaskCategory] = useState<WorkbenchTaskCategory>("scheduled");
|
||||
const [scheduleOpen, setScheduleOpen] = useState(false);
|
||||
const [scheduleTitle, setScheduleTitle] = useState("");
|
||||
const [scheduleKey, setScheduleKey] = useState("scada-diagnosis");
|
||||
const [scheduleSummary, setScheduleSummary] = useState("");
|
||||
const [scheduleIntervalMinutes, setScheduleIntervalMinutes] = useState("15");
|
||||
const [scheduleIntervalPreset, setScheduleIntervalPreset] = useState("15");
|
||||
const [scheduleNextRunAt, setScheduleNextRunAt] = useState(() => formatLocalDateTimeInput(new Date(Date.now() + 15 * 60_000)));
|
||||
const [scheduleSubmitting, setScheduleSubmitting] = useState(false);
|
||||
const [scheduleError, setScheduleError] = useState<string | null>(null);
|
||||
const mobileSheet = presentation === "mobile-sheet";
|
||||
const desktopDock = presentation === "desktop-dock";
|
||||
const compactPresentation = mobileSheet || desktopDock;
|
||||
const desktopFloating = presentation === "desktop-floating";
|
||||
const expanded = controlledExpanded ?? uncontrolledExpanded;
|
||||
const selectedConditionId = controlledSelectedConditionId ?? uncontrolledSelectedConditionId;
|
||||
const { recentConditions: allRecentConditions, futureWorkOrders: allFutureWorkOrders } = useMemo(
|
||||
() => groupScheduledConditions(conditions),
|
||||
const conditionsByCategory = useMemo(
|
||||
() => groupConditionsByTaskCategory(conditions),
|
||||
[conditions]
|
||||
);
|
||||
const categoryConditions = conditionsByCategory[taskCategory];
|
||||
const activeLoading = taskCategory === "scheduled" ? loading : backendLoading;
|
||||
const { recentConditions: allRecentConditions, futureWorkOrders: allFutureWorkOrders } = useMemo(
|
||||
() => groupScheduledConditions(categoryConditions),
|
||||
[categoryConditions]
|
||||
);
|
||||
const allTimelineConditions = useMemo(
|
||||
() => [...allRecentConditions, ...allFutureWorkOrders],
|
||||
[allFutureWorkOrders, allRecentConditions]
|
||||
);
|
||||
const taskFilterOptions = useMemo(() => createTaskFilterOptions(conditions), [conditions]);
|
||||
const taskFilterOptions = useMemo(
|
||||
() => createTaskFilterOptions(categoryConditions),
|
||||
[categoryConditions]
|
||||
);
|
||||
const taskFilteredConditions = useMemo(
|
||||
() =>
|
||||
conditions.filter(
|
||||
categoryConditions.filter(
|
||||
(condition) => taskFilter === CONDITION_FILTER_ALL || condition.title === taskFilter
|
||||
),
|
||||
[conditions, taskFilter]
|
||||
[categoryConditions, taskFilter]
|
||||
);
|
||||
const statusFilterOptions = useMemo(
|
||||
() => createStatusFilterOptions(taskFilteredConditions),
|
||||
@@ -150,24 +209,9 @@ export function ScheduledConditionFeed({
|
||||
const filterActive = taskFilter !== CONDITION_FILTER_ALL || statusFilter !== CONDITION_FILTER_ALL;
|
||||
const totalConditionCount = allRecentConditions.length + allFutureWorkOrders.length;
|
||||
const filteredConditionCount = recentConditions.length + futureWorkOrders.length;
|
||||
const conditionSourceLabel = {
|
||||
mock: "工况演示数据",
|
||||
backend: "后端当天数据",
|
||||
scheduled_tasks: "后端定时任务"
|
||||
}[dataSource];
|
||||
const workOrderSourceLabel = {
|
||||
mock: "工单演示回退",
|
||||
backend: "后端工单",
|
||||
none: "未接入工单"
|
||||
}[workOrderDataSource];
|
||||
const sourceLabel = `${conditionSourceLabel} · ${workOrderSourceLabel}`;
|
||||
const headerSummary = loading
|
||||
const headerSummary = loading && backendLoading
|
||||
? "正在加载工况任务"
|
||||
: expanded && filterActive
|
||||
? `筛选 ${filteredConditionCount}/${totalConditionCount} 条 · 预约 ${futureWorkOrders.length}/${allFutureWorkOrders.length} 条 · ${sourceLabel}`
|
||||
: expanded
|
||||
? `最近 ${recentConditions.length} 条 · 预约 ${futureWorkOrders.length} 条 · ${sourceLabel}`
|
||||
: `最近 ${recentConditions.length} 条 · 预约 ${futureWorkOrders.length} 条 · ${sourceLabel}`;
|
||||
: `定时 ${conditionsByCategory.scheduled.length} · 调查 ${conditionsByCategory.investigation.length} · 预约 ${conditionsByCategory.appointment.length}`;
|
||||
const selectedCondition =
|
||||
timelineConditions.find((condition) => condition.id === selectedConditionId) ??
|
||||
recentConditions[0] ??
|
||||
@@ -222,20 +266,24 @@ export function ScheduledConditionFeed({
|
||||
return;
|
||||
}
|
||||
|
||||
const targetCondition = allTimelineConditions.find(
|
||||
const targetCondition = conditions.find(
|
||||
(condition) => condition.id === focusConditionId
|
||||
);
|
||||
if (!targetCondition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetCategory = getWorkbenchTaskCategory(targetCondition);
|
||||
if (!targetCategory) return;
|
||||
|
||||
setTaskCategory(targetCategory);
|
||||
setTaskFilter(CONDITION_FILTER_ALL);
|
||||
setStatusFilter(CONDITION_FILTER_ALL);
|
||||
updateSelectedConditionId(targetCondition.id);
|
||||
updateExpanded(true);
|
||||
onFocusRequestHandled?.(focusRequestId);
|
||||
}, [
|
||||
allTimelineConditions,
|
||||
conditions,
|
||||
focusConditionId,
|
||||
focusRequestId,
|
||||
onFocusRequestHandled,
|
||||
@@ -250,6 +298,42 @@ export function ScheduledConditionFeed({
|
||||
);
|
||||
}
|
||||
|
||||
function handleTaskCategoryChange(nextCategory: WorkbenchTaskCategory) {
|
||||
setTaskCategory(nextCategory);
|
||||
if (nextCategory !== "scheduled") setScheduleOpen(false);
|
||||
setTaskFilter(CONDITION_FILTER_ALL);
|
||||
setStatusFilter(CONDITION_FILTER_ALL);
|
||||
updateSelectedConditionId(conditionsByCategory[nextCategory][0]?.id ?? null);
|
||||
}
|
||||
|
||||
function handleToggleSchedule() {
|
||||
if (!scheduleOpen) {
|
||||
setTaskCategory("scheduled");
|
||||
updateExpanded(true);
|
||||
}
|
||||
setScheduleOpen((current) => !current);
|
||||
}
|
||||
|
||||
async function handleCreateSchedule() {
|
||||
const title = scheduleTitle.trim();
|
||||
const summary = scheduleSummary.trim();
|
||||
const key = scheduleKey.trim();
|
||||
const intervalMinutes = Number(scheduleIntervalMinutes);
|
||||
const nextRunAt = new Date(scheduleNextRunAt);
|
||||
if (!title || !summary || !key) { setScheduleError("请填写名称、流程和任务说明。"); return; }
|
||||
if (!Number.isInteger(intervalMinutes) || intervalMinutes < 1) { setScheduleError("执行周期不能少于 1 分钟。"); return; }
|
||||
if (Number.isNaN(nextRunAt.getTime())) { setScheduleError("请选择有效的下次执行时间。"); return; }
|
||||
if (!onCreateSchedule) { setScheduleError("当前未连接定时流程配置接口。"); return; }
|
||||
setScheduleSubmitting(true); setScheduleError(null);
|
||||
try {
|
||||
await onCreateSchedule({ scheduleKey: key, title, summary, intervalSeconds: intervalMinutes * 60, nextRunAt: nextRunAt.toISOString() });
|
||||
setScheduleOpen(false); setScheduleTitle(""); setScheduleSummary("");
|
||||
showMapNotice({ tone: "success", title: "定时任务已添加", message: "仅保存在当前演示工作台,不会提交后端调度。" });
|
||||
} catch (error) {
|
||||
setScheduleError(error instanceof Error ? error.message : "定时流程创建失败。");
|
||||
} finally { setScheduleSubmitting(false); }
|
||||
}
|
||||
|
||||
function handleSelectTimelineCondition(conditionId: string) {
|
||||
updateSelectedConditionId(conditionId);
|
||||
if (!expanded) {
|
||||
@@ -262,6 +346,18 @@ export function ScheduledConditionFeed({
|
||||
return;
|
||||
}
|
||||
|
||||
if (condition.operationalTask) {
|
||||
onExpandAgent();
|
||||
void Promise.resolve(onOpenTaskCollaboration?.(condition.operationalTask.task_id))
|
||||
.catch((error) => showMapNotice({
|
||||
id: "operational-task-collaboration",
|
||||
tone: "error",
|
||||
title: "Agent 协作打开失败",
|
||||
message: error instanceof Error ? error.message : "无法打开这条任务的协作问答。"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
onExpandAgent();
|
||||
|
||||
if (!condition.sessionId) {
|
||||
@@ -346,6 +442,17 @@ export function ScheduledConditionFeed({
|
||||
<h2 className="truncate text-sm font-semibold leading-tight text-slate-950">工况任务</h2>
|
||||
<p className="mt-0.5 truncate text-xs text-slate-500">{headerSummary}</p>
|
||||
</div>
|
||||
{onCreateSchedule ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="配置定时流程"
|
||||
aria-expanded={scheduleOpen}
|
||||
onClick={handleToggleSchedule}
|
||||
className="grid h-10 w-10 shrink-0 place-items-center rounded-lg text-slate-500 transition-colors active:scale-95 [@media(hover:hover)]:hover:bg-blue-50 [@media(hover:hover)]:hover:text-blue-700"
|
||||
>
|
||||
<CalendarPlus size={17} aria-hidden="true" />
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={expanded}
|
||||
@@ -375,6 +482,38 @@ export function ScheduledConditionFeed({
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
{expanded ? (
|
||||
<TaskCategorySwitch
|
||||
activeCategory={taskCategory}
|
||||
counts={{
|
||||
scheduled: conditionsByCategory.scheduled.length,
|
||||
investigation: conditionsByCategory.investigation.length,
|
||||
appointment: conditionsByCategory.appointment.length
|
||||
}}
|
||||
onChange={handleTaskCategoryChange}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{scheduleOpen ? (
|
||||
<section aria-label="配置定时流程" className={cn("mt-2 p-3", CONDITION_CONTENT_SURFACE_CLASS_NAME)}>
|
||||
<label className="block text-xs font-medium text-slate-600">定时流程名称<Input className="mt-1 h-9" placeholder="例如夜间低压巡检" value={scheduleTitle} onChange={(event) => setScheduleTitle(event.target.value)} /></label>
|
||||
<div className="mt-2"><FieldSelect label="执行流程" value={scheduleKey} onValueChange={setScheduleKey} options={[{ value: "scada-diagnosis", label: "SCADA 数据诊断" }, { value: "smart-dispatch", label: "管网智能调度" }, { value: "network-simulation", label: "管网定时模拟" }, { value: "pump-energy", label: "泵站能耗检查" }]} /></div>
|
||||
<label className="mt-2 block text-xs font-medium text-slate-600">任务说明<Textarea className="mt-1 min-h-16" placeholder="说明本流程核查什么以及预期输出" value={scheduleSummary} onChange={(event) => setScheduleSummary(event.target.value)} /></label>
|
||||
<div className="mt-2 grid gap-2 sm:grid-cols-2">
|
||||
<div>
|
||||
<FieldSelect label="执行周期" value={scheduleIntervalPreset} onValueChange={(value) => { setScheduleIntervalPreset(value); if (value !== "custom") setScheduleIntervalMinutes(value); }} options={[{ value: "5", label: "每 5 分钟" }, { value: "15", label: "每 15 分钟" }, { value: "30", label: "每 30 分钟" }, { value: "60", label: "每 60 分钟" }, { value: "custom", label: "自定义周期" }]} />
|
||||
{scheduleIntervalPreset === "custom" ? <label className="mt-2 block text-xs font-medium text-slate-600">自定义分钟数<Input className="mt-1 h-9" inputMode="numeric" value={scheduleIntervalMinutes} onChange={(event) => setScheduleIntervalMinutes(event.target.value.replace(/\D/g, ""))} /></label> : null}
|
||||
</div>
|
||||
<DateTimeField label="下次执行时间" value={scheduleNextRunAt} onChange={setScheduleNextRunAt} />
|
||||
</div>
|
||||
{scheduleError ? <p role="alert" className="mt-2 text-xs leading-5 text-red-700">{scheduleError}</p> : null}
|
||||
<div className="mt-2 flex justify-end gap-2">
|
||||
<Button type="button" size="sm" variant="outline" disabled={scheduleSubmitting} onClick={() => setScheduleOpen(false)}>取消</Button>
|
||||
<Button type="button" size="sm" disabled={scheduleSubmitting} onClick={() => void handleCreateSchedule()}>{scheduleSubmitting ? "创建中" : "创建定时流程"}</Button>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"scheduled-feed-layout mt-3 grid min-h-0",
|
||||
@@ -398,7 +537,7 @@ export function ScheduledConditionFeed({
|
||||
)}
|
||||
aria-hidden={!expanded}
|
||||
>
|
||||
{loading && expanded ? (
|
||||
{activeLoading && expanded ? (
|
||||
<ConditionDetailSkeleton />
|
||||
) : expanded && selectedCondition ? (
|
||||
<ConditionDetailPanel
|
||||
@@ -406,6 +545,11 @@ export function ScheduledConditionFeed({
|
||||
dataSource={dataSource}
|
||||
workOrderDataSource={workOrderDataSource}
|
||||
onContinueConversation={() => handleContinueConversation(selectedCondition)}
|
||||
onTaskDecision={onTaskDecision}
|
||||
onTaskExecute={onTaskExecute}
|
||||
onTaskReconcile={onTaskReconcile}
|
||||
onLoadTaskEvidenceSnapshot={onLoadTaskEvidenceSnapshot}
|
||||
onTaskRefresh={onTaskRefresh}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -418,7 +562,17 @@ export function ScheduledConditionFeed({
|
||||
<ConditionTimelinePanel
|
||||
expanded={expanded}
|
||||
presentation={presentation}
|
||||
loading={loading}
|
||||
loading={activeLoading}
|
||||
emptyTitle={
|
||||
backendError && taskCategory !== "scheduled"
|
||||
? `${WORKBENCH_TASK_CATEGORIES.find((item) => item.id === taskCategory)?.label ?? "后端任务"}暂不可用`
|
||||
: undefined
|
||||
}
|
||||
emptyDescription={
|
||||
backendError && taskCategory !== "scheduled"
|
||||
? backendError
|
||||
: undefined
|
||||
}
|
||||
recentConditions={recentConditions}
|
||||
futureWorkOrders={futureWorkOrders}
|
||||
taskFilter={taskFilter}
|
||||
@@ -443,6 +597,54 @@ export function ScheduledConditionFeed({
|
||||
);
|
||||
}
|
||||
|
||||
function TaskCategorySwitch({
|
||||
activeCategory,
|
||||
counts,
|
||||
onChange
|
||||
}: {
|
||||
activeCategory: WorkbenchTaskCategory;
|
||||
counts: Record<WorkbenchTaskCategory, number>;
|
||||
onChange: (category: WorkbenchTaskCategory) => void;
|
||||
}) {
|
||||
return (
|
||||
<nav
|
||||
aria-label="工况任务分类"
|
||||
className={cn(CONDITION_CONTROL_SURFACE_CLASS_NAME, "mt-2 grid grid-cols-3 gap-1 p-1")}
|
||||
>
|
||||
{WORKBENCH_TASK_CATEGORIES.map((category) => {
|
||||
const active = category.id === activeCategory;
|
||||
return (
|
||||
<button
|
||||
key={category.id}
|
||||
type="button"
|
||||
aria-current={active ? "page" : undefined}
|
||||
onClick={() => onChange(category.id)}
|
||||
className={cn(
|
||||
"min-h-12 min-w-0 rounded-lg px-1.5 py-1.5 text-left transition-[background-color,color,box-shadow] duration-150 motion-reduce:transition-none! focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-200",
|
||||
active
|
||||
? "bg-white text-slate-900 shadow-[0_1px_3px_rgba(15,23,42,0.1)]"
|
||||
: "text-slate-500 [@media(hover:hover)]:hover:bg-white/70 [@media(hover:hover)]:hover:text-slate-800"
|
||||
)}
|
||||
>
|
||||
<span className="flex min-w-0 items-center justify-between gap-1">
|
||||
<span className="truncate text-xs font-semibold">{category.label}</span>
|
||||
<span className="shrink-0 text-xs tabular-nums text-slate-400">{counts[category.id]}</span>
|
||||
</span>
|
||||
<span className="mt-0.5 block truncate text-[10px] leading-3 text-slate-400">
|
||||
{category.sourceLabel}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
function formatLocalDateTimeInput(value: Date) {
|
||||
const local = new Date(value.getTime() - value.getTimezoneOffset() * 60_000);
|
||||
return local.toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
type ConditionTimelinePanelProps = {
|
||||
expanded: boolean;
|
||||
presentation: ScheduledConditionFeedPresentation;
|
||||
@@ -457,6 +659,8 @@ type ConditionTimelinePanelProps = {
|
||||
totalConditionCount: number;
|
||||
taskFilteredConditionCount: number;
|
||||
filteredConditionCount: number;
|
||||
emptyTitle?: string;
|
||||
emptyDescription?: string;
|
||||
onTaskFilterChange: (value: ConditionTaskFilterValue) => void;
|
||||
onStatusFilterChange: (value: ConditionStatusFilterValue) => void;
|
||||
onResetFilters: () => void;
|
||||
@@ -477,6 +681,8 @@ function ConditionTimelinePanel({
|
||||
totalConditionCount,
|
||||
taskFilteredConditionCount,
|
||||
filteredConditionCount,
|
||||
emptyTitle,
|
||||
emptyDescription,
|
||||
onTaskFilterChange,
|
||||
onStatusFilterChange,
|
||||
onResetFilters,
|
||||
@@ -545,11 +751,11 @@ function ConditionTimelinePanel({
|
||||
/>
|
||||
) : (
|
||||
<ConditionEmptyState
|
||||
title={filterActive ? "没有符合筛选的最近工况" : "暂无最近工况"}
|
||||
title={emptyTitle ?? (filterActive ? "没有符合筛选的任务" : "暂无任务")}
|
||||
description={
|
||||
filterActive
|
||||
? "调整任务或状态筛选后查看其他工况记录。"
|
||||
: "新的工况任务完成后会出现在这里。"
|
||||
emptyDescription ?? (filterActive
|
||||
? "调整任务或状态筛选后查看其他记录。"
|
||||
: "新任务进入当前分类后会出现在这里。")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user