feat(会商第一步): 分析记录按业务链筛选(漏斗下拉, 选项从草稿chainTitle去重)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
fengxiangdebaoziandClaude Opus 4.8 committed 2026-06-25 18:08:36 +08:00
1 parent 368673c3b4
commit 27fffbec61
1 file changed
+95 -2
@@ -40,6 +40,7 @@ import {
Clock,
Cpu,
Download,
Filter,
History,
Layers,
ListOrdered,
@@ -61,6 +62,14 @@ import {
import { useTheme } from "@/contexts/ThemeContext";
import { useArtifacts } from "@/components/workspace/artifacts";
import { useSidebarSafe } from "@/components/ui/sidebar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { desensitize } from "@/lib/desensitize";
import { cn } from "@/lib/utils";
import { useDesensitizeSubtree } from "@/roundtable-planning/lib/use-desensitize-subtree";
@@ -2860,6 +2869,10 @@ function getChainStageGroups(chain: BusinessChain): BusinessChain["seats"][] {
return chain.seats.map((s) => [s]);
}
/** 分析记录「按业务链筛选」的两个特殊值:全部 / 无业务链(自由模式)。 */
const RECORD_FILTER_ALL = "__all__";
const RECORD_FILTER_FREE = "__free__";
/**
* 需求 0616 #2:多智能体会商 Step1(落地)左栏入口栏。
* - 业务链:仅公共链条(``listChains("public")``)前 3 个;点击以该链席位启动会商。
@@ -2886,6 +2899,38 @@ function RoundtableEntryRail({
const [chains, setChains] = useState<BusinessChain[]>([]);
const [expandedChainId, setExpandedChainId] = useState<string | null>(null);
const { drafts: records, refresh } = useRoundtableDrafts(taskId);
// 分析记录「按业务链筛选」—— 选项从已加载草稿的 chainTitle 去重得到,
// 无业务链的草稿(自由 / recommend 模式)归入「自由模式」。
const [chainFilter, setChainFilter] = useState<string>(RECORD_FILTER_ALL);
const chainFilterOptions = useMemo(() => {
const titles = new Set<string>();
let hasFree = false;
for (const rec of records) {
const title = rec.chainTitle?.trim();
if (title) titles.add(title);
else hasFree = true;
}
const opts = Array.from(titles)
.sort((a, b) => a.localeCompare(b, "zh-CN"))
.map((title) => ({ value: title, label: title }));
if (hasFree) opts.push({ value: RECORD_FILTER_FREE, label: "自由模式" });
return opts;
}, [records]);
const filteredRecords = useMemo(() => {
if (chainFilter === RECORD_FILTER_ALL) return records;
if (chainFilter === RECORD_FILTER_FREE)
return records.filter((rec) => !rec.chainTitle?.trim());
return records.filter((rec) => rec.chainTitle?.trim() === chainFilter);
}, [records, chainFilter]);
// 草稿刷新后所选业务链可能已不存在(被删/改名)→ 回落到「全部」,避免列表空白无解。
useEffect(() => {
if (
chainFilter !== RECORD_FILTER_ALL &&
!chainFilterOptions.some((opt) => opt.value === chainFilter)
) {
setChainFilter(RECORD_FILTER_ALL);
}
}, [chainFilter, chainFilterOptions]);
// 深链接(taskId)模式:左上角不显示「业务链选择」(链条已由 businessCode 自动决定),改为展示
// 当前任务(rwfx/3qfx)或当前行动(xdfx)。xdfx 的根是「行动」,故展示行动名称 + 叙事。
const deeplink = Boolean(taskId);
@@ -3159,13 +3204,59 @@ function RoundtableEntryRail({
<History className="w-3 h-3" strokeWidth={2.5} />
</span>
<h2 className="text-sm font-semibold tracking-wide text-slate-900">分析记录</h2>
{/* 按业务链筛选 —— 仅当存在多于 1 个可选业务链时才显示图标按钮。 */}
{chainFilterOptions.length > 1 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
title="按业务链筛选"
aria-label="按业务链筛选"
className={cn(
"ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-md border transition-colors",
chainFilter === RECORD_FILTER_ALL
? "border-[#ECE7E4] text-neutral-400 hover:border-primary/30 hover:text-primary"
: "border-primary/30 bg-primary/5 text-primary",
)}
>
<Filter className="h-3 w-3" strokeWidth={2.5} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[12rem]">
<DropdownMenuLabel className="text-[16px] text-neutral-500">
按业务链筛选
</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={chainFilter}
onValueChange={setChainFilter}
>
<DropdownMenuRadioItem value={RECORD_FILTER_ALL} className="text-[16px]">
全部
</DropdownMenuRadioItem>
{chainFilterOptions.map((opt) => (
<DropdownMenuRadioItem
key={opt.value}
value={opt.value}
className="text-[16px]"
>
<span className="truncate">{opt.label}</span>
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
{records.length === 0 ? (
<p className="flex-1 text-xs text-neutral-400 text-center py-8">暂无分析记录</p>
) : filteredRecords.length === 0 ? (
<p className="flex-1 text-xs text-neutral-400 text-center py-8">
该业务链下暂无分析记录
</p>
) : (
<>
<div className="space-y-2 overflow-y-auto flex-1 px-1 py-0.5 custom-scrollbar">
{records.map((rec) => {
{filteredRecords.map((rec) => {
// 已完成:进行到第 3 步(执行过方案总结报告)
const completed = rec.furthestStep === 3;
// 选中:当前已加载的草稿(正在查看的那条)
@@ -3230,7 +3321,9 @@ function RoundtableEntryRail({
})}
</div>
<div className="mt-2 pt-2 border-t border-neutral-100 text-center text-[11px] text-neutral-400">
共 {records.length} 条分析记录
{chainFilter === RECORD_FILTER_ALL
? `共 ${records.length} 条分析记录`
: `筛选出 ${filteredRecords.length} / ${records.length} 条分析记录`}
</div>
</>
)}