import type { ChapterListItem } from "@/lib/api/types"; import type { ChapterEntry } from "@/lib/workbench/chapter"; import { buildChapterEntries } from "@/lib/workbench/chapter"; export interface ReviewChapterOption { no: number; label: string; // 有草稿正文=可审;无草稿章置灰(审稿依赖草稿,选空章会 422)。 disabled: boolean; } export function reviewChapterHref(projectId: string, chapterNo: number): string { return `/projects/${projectId}/review?chapter=${chapterNo}`; } // 旧签名:仅按大纲章 + 当前章构建(无草稿状态)。保留兼容,无 chapterList 时回退用。 export function reviewChapterOptions( chapters: readonly ChapterEntry[], currentChapterNo: number, ): ReviewChapterOption[] { return buildChapterEntries(chapters, currentChapterNo).map((chapter) => ({ no: chapter.no, label: chapter.title ? `第 ${chapter.no} 章 · ${chapter.title}` : `第 ${chapter.no} 章`, disabled: false, })); } function statusSuffix(item: ChapterListItem | undefined): string { if (!item) return ""; if (item.accepted) return " · 已验收"; if (item.reviewed_at) return " · 已审"; if (item.has_draft) return " · 草稿"; return " · 空"; } // 新签名:以「真实存在的章」(chapterList) 为准枚举,覆盖「写过但不在大纲」的章, // 用大纲标题补短名,并标注可审/已审/已验收;无草稿章置灰(但当前章永不置灰)。 export function buildReviewChapterOptions( chapterList: readonly ChapterListItem[], outline: readonly ChapterEntry[], currentChapterNo: number, ): ReviewChapterOption[] { const titleByNo = new Map(outline.map((c) => [c.no, c.title])); const itemByNo = new Map(chapterList.map((c) => [c.chapter_no, c])); // 枚举真实写过的章(chapterList) ∪ 大纲章(outline,展示全书结构) ∪ 当前章。 const nos = new Set([ ...chapterList.map((c) => c.chapter_no), ...outline.map((c) => c.no), currentChapterNo, ]); return [...nos] .sort((a, b) => a - b) .map((no) => { const item = itemByNo.get(no); const title = titleByNo.get(no); const base = title ? `第 ${no} 章 · ${title}` : `第 ${no} 章`; return { no, label: base + statusSuffix(item), // 无草稿的章不可审(审稿依赖草稿);仅当前章例外,始终可停留。 disabled: no !== currentChapterNo && !item?.has_draft, }; }); } // 无 ?chapter 时的默认章:优先最近一个有草稿的章,其次最大章号,兜底第 1 章。 export function defaultReviewChapter( chapterList: readonly ChapterListItem[], ): number { const withDraft = chapterList .filter((c) => c.has_draft) .map((c) => c.chapter_no); if (withDraft.length > 0) return Math.max(...withDraft); if (chapterList.length > 0) { return Math.max(...chapterList.map((c) => c.chapter_no)); } return 1; }