/** * 任务列表区 —— 自 `~/coding/JCAI_PRD/workbench/(proto)/page.tsx` 平移。 * * **DOM 结构与类名原样保留**(`jc-*` 前缀、vendor 皮肤一并搬); * 只把 mock 数据换成服务端投影,渲染输出保持 1:1(README 平移规则 1/2/4)。 * * 「清单里一条 = 一个任务 = 一个活动的一次运行,不是一整条工作流。」 */ import { useState } from 'react'; import type { TaskCard } from '@contracts'; import { TERMINAL_STATE_LABEL } from '@contracts'; import { Ico } from '@web-shared/Ico.tsx'; import { ListEmpty } from '@web-shared/desk/module-kit.tsx'; import ScrollArea from '@web-shared/ScrollArea.tsx'; const TASK_COLORS = ['#3370ff', '#ff8800', '#7f3bf5', '#0aa294', '#f14ba9']; type Status = 'active' | 'done'; export type TaskListProps = { readonly tasks: readonly TaskCard[]; readonly selectedId: string; readonly collapsed: boolean; onPick(id: string): void; onCollapse(): void; }; const statusOf = (task: TaskCard): Status => (task.terminal === undefined ? 'active' : 'done'); export function TaskList({ tasks, selectedId, collapsed, onPick, onCollapse }: TaskListProps) { const [filter, setFilter] = useState('active'); const [query, setQuery] = useState(''); if (collapsed) return null; const count = (s: Status) => tasks.filter((t) => statusOf(t) === s).length; const q = query.trim().toLowerCase(); const visible = tasks.filter( (t) => statusOf(t) === filter && (q === '' || `${t.title} ${t.activity} ${t.workflowSubject}`.toLowerCase().includes(q)), ); return ( ); } /** * 任务卡:定高信息卡 —— 图标 + 活动名 + 状态章 / 主题两行位 / 编号 + 流内序位 / 进度条。 * 每行都有硬约束(单行省略或两行位),信息丰富但永不超限。 */ function TaskCardView({ task, index, selected, onPick, }: { task: TaskCard; index: number; selected: boolean; onPick: () => void; }) { const done = task.terminal !== undefined; /* * 进度条读**活动自己的阶段**,不读工作流序位。 * * 序位答的是「这单在整条流的第几个活动」——A1 在七节点流里恒 1/7, * 从起单到收口一动不动,画成进度条就是一条永远 14% 的线; * 单活动试跑更荒唐:没有工作流,序位恒 1/1,起单那一刻就满格。 * 活动没声明阶段就**不画** —— 编一个假的比不画更坏。 */ const phase = task.phase; const pct = done ? 100 : phase === undefined ? undefined : Math.round((phase.index / phase.total) * 100); return ( ); }