af91d843d8
- ai-plus: Class组件→函数组件,接入 listHealthScores + listAgentExecutions API - ooda: 硬编码→接入 risks/weak-signals/sentinels/tasks API 构建OODA各阶段 - threads: 硬编码→接入 risks/tasks/events API 组合决策线程 - today: 硬编码→接入 risks/weak-signals/synergies/reports API 构建行动项 - compare: 硬编码企业名→使用全局企业列表 + listHealthScores + listRisks API - innovation/knowledge-graph/portfolio: 加 useCompanyScope 标题显示企业名 - 所有页面标题在单企业选择时显示企业名 - 编译验证通过
121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
/** 决策线程列表页 — 按状态/企业筛选 + 线程卡片,数据来自后端 API。 */
|
||
|
||
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { GitBranch } from "lucide-react";
|
||
import { ThreadList, type ThreadItem } from "@/components/threads/ThreadList";
|
||
import { useCompanyScope, useScopeEffect, useCompanyName } from "@/lib/company-scope";
|
||
import { listRisks, type RiskEvent } from "@/lib/risks";
|
||
import { listTasks } from "@/lib/api-v2";
|
||
import { listMajorEvents } from "@/lib/api-v2";
|
||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||
import { EmptyState } from "@/components/shared/EmptyState";
|
||
|
||
/** 任务项。 */
|
||
interface TaskItem {
|
||
id: string;
|
||
company_id?: string;
|
||
title: string;
|
||
status?: string;
|
||
}
|
||
|
||
/** 重大事项项。 */
|
||
interface MajorEventItem {
|
||
id: string;
|
||
company_id?: string;
|
||
event_type: string;
|
||
title: string;
|
||
}
|
||
|
||
/** 决策线程列表页面。 */
|
||
export default function ThreadsPage() {
|
||
const { companyName } = useCompanyScope();
|
||
const getCompanyName = useCompanyName();
|
||
const [threads, setThreads] = useState<ThreadItem[]>([]);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
|
||
useScopeEffect(() => {
|
||
Promise.all([
|
||
listRisks({ page_size: 50 }),
|
||
listTasks(),
|
||
listMajorEvents(),
|
||
])
|
||
.then(([risksResp, tasksResp, eventsResp]) => {
|
||
const risks = (risksResp.data?.items as RiskEvent[]) ?? [];
|
||
const tasks = (tasksResp.data as TaskItem[]) ?? [];
|
||
const events = (eventsResp.data as MajorEventItem[]) ?? [];
|
||
|
||
// 将风险、任务、事件组合为决策线程
|
||
const combined: ThreadItem[] = [];
|
||
|
||
// 风险 → 线程
|
||
risks.forEach((risk) => {
|
||
combined.push({
|
||
id: `risk-${risk.id}`,
|
||
title: risk.title,
|
||
company: getCompanyName(risk.company_id),
|
||
status: risk.status === "closed" ? "closed" : risk.status === "in_progress" ? "acted" : risk.status === "assigned" ? "identified" : "analyzing",
|
||
updatedAt: risk.updated_at?.slice(0, 10) ?? "",
|
||
riskCount: 1,
|
||
});
|
||
});
|
||
|
||
// 重大事项 → 线程
|
||
events.forEach((event) => {
|
||
combined.push({
|
||
id: `event-${event.id}`,
|
||
title: event.title,
|
||
company: getCompanyName(event.company_id),
|
||
status: "identified",
|
||
updatedAt: "",
|
||
riskCount: 0,
|
||
});
|
||
});
|
||
|
||
// 任务 → 线程
|
||
tasks.forEach((task) => {
|
||
combined.push({
|
||
id: `task-${task.id}`,
|
||
title: task.title,
|
||
company: getCompanyName(task.company_id),
|
||
status: task.status === "completed" ? "closed" : task.status === "in_progress" ? "acted" : "identified",
|
||
updatedAt: "",
|
||
riskCount: 0,
|
||
});
|
||
});
|
||
|
||
setThreads(combined.slice(0, 20));
|
||
})
|
||
.catch(() => {
|
||
setThreads([]);
|
||
})
|
||
.finally(() => setIsLoading(false));
|
||
});
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center gap-2">
|
||
<GitBranch className="text-[var(--investor-primary)]" size={24} />
|
||
<h1 className="text-2xl font-bold">
|
||
决策线程{companyName ? ` — ${companyName}` : ""}
|
||
</h1>
|
||
</div>
|
||
|
||
<div className="rounded-lg border bg-white p-4 shadow-sm">
|
||
<p className="text-sm text-muted-foreground">
|
||
决策线程将风险、建议、任务串联为可追踪的决策链路,支持状态机驱动的时间线视图。
|
||
</p>
|
||
</div>
|
||
|
||
{isLoading ? (
|
||
<LoadingSpinner />
|
||
) : threads.length === 0 ? (
|
||
<EmptyState description="暂无决策线程" />
|
||
) : (
|
||
<ThreadList threads={threads} />
|
||
)}
|
||
</div>
|
||
);
|
||
}
|