Files
AIPortPilot/frontend/src/app/(investor)/threads/page.tsx
T
selfrelease af91d843d8 feat(frontend): 全部页面接入后端API并同步全局企业选择
- 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 标题显示企业名
- 所有页面标题在单企业选择时显示企业名
- 编译验证通过
2026-07-20 08:40:54 +08:00

121 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 决策线程列表页 — 按状态/企业筛选 + 线程卡片,数据来自后端 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>
);
}