// 「本章伏笔」主动提醒的纯分类逻辑(写作台右栏 / 速查抽屉共用)。 // 输入全量伏笔 + 当前章号,用 board 的 isWindowApproaching + status 分三类。 // 纯函数、不可变(返回全新对象,不改动入参);便于 node 环境单测。 import type { ForeshadowView } from "@/lib/api/types"; import { isWindowApproaching, LANE_LABELS, type ForeshadowStatus } from "./board"; // 本章伏笔三分类: // - recyclable 可回收:本章落在 [from,to] 窗口内(OPEN/PARTIAL,由 isWindowApproaching 判定)。 // - pending 待回收:OPEN/PARTIAL 但尚未进入回收窗口(窗口在后 / 未设窗口)。 // - overdue 已逾期:status==OVERDUE(后端验收后扫描得出,略滞后于当前草稿)。 // CLOSED(已回收)与未知 status 不进任何一类——本章无需再提醒。 export interface ChapterForeshadowGroups { recyclable: ForeshadowView[]; pending: ForeshadowView[]; overdue: ForeshadowView[]; } // 按当前章号把伏笔分三类。纯函数:用 reduce + 展开返回新对象,不可变。 // 种子每次新建(不复用共享常量),空输入也返回独立的新对象。 export function classifyChapterForeshadow( items: readonly ForeshadowView[] | undefined, currentChapter: number, ): ChapterForeshadowGroups { return (items ?? []).reduce( (groups, item) => { if (item.status === "OVERDUE") { return { ...groups, overdue: [...groups.overdue, item] }; } if (isWindowApproaching(item, currentChapter)) { return { ...groups, recyclable: [...groups.recyclable, item] }; } if (item.status === "OPEN" || item.status === "PARTIAL") { return { ...groups, pending: [...groups.pending, item] }; } return groups; // CLOSED / 未知 status:本章不提醒 }, { recyclable: [], pending: [], overdue: [] }, ); } // 三类合计条数(是否有可提醒项,便于空态判断)。 export function chapterForeshadowTotal(groups: ChapterForeshadowGroups): number { return groups.recyclable.length + groups.pending.length + groups.overdue.length; } // 单条伏笔的作者向状态文案(复用看板 LANE_LABELS;未知 status 回退原值)。 export function foreshadowStatusLabel(status: string): string { return LANE_LABELS[status as ForeshadowStatus] ?? status; }