Files
writer-work-flow/apps/web/lib/review/useAccept.ts

79 lines
2.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.

"use client";
import { useCallback, useState } from "react";
import { api } from "@/lib/api/client";
import { useToast } from "@/components/Toast";
import type { AcceptResponse } from "@/lib/api/types";
import { buildAcceptRequest, type DecisionDraft } from "./decisions";
export type AcceptStatus = "idle" | "accepting" | "accepted" | "error";
export interface AcceptOutcome {
result: AcceptResponse | null;
// 409 CONFLICT_UNRESOLVED 缺判下标(高亮报告卡)。
missingIndices: number[];
// 后端验收闸报「有未裁决冲突」——即便本页当前未显示冲突(快照过期/未审稿)。
// 调用方据此回拉持久化最新审稿、回灌冲突供裁决(修「显示 0 冲突却被拦」)。
conflictUnresolved: boolean;
}
export interface UseAccept {
status: AcceptStatus;
result: AcceptResponse | null;
accept: (
projectId: string,
chapterNo: number,
finalText: string,
drafts: readonly DecisionDraft[],
) => Promise<AcceptOutcome>;
}
interface ApiErrorEnvelope {
error?: {
code?: string;
message?: string;
details?: { missing_conflict_indices?: number[] } | null;
};
}
// 验收:乐观置 accepting → 成功置 accepted呈现「本次将更新」清单
// 失败回滚状态 + toast409 CONFLICT_UNRESOLVED 解析缺判下标供高亮。
export function useAccept(): UseAccept {
const [status, setStatus] = useState<AcceptStatus>("idle");
const [result, setResult] = useState<AcceptResponse | null>(null);
const toast = useToast();
const accept = useCallback<UseAccept["accept"]>(
async (projectId, chapterNo, finalText, drafts) => {
setStatus("accepting");
const body = buildAcceptRequest(finalText, drafts);
const { data, error } = await api.POST(
"/projects/{project_id}/chapters/{chapter_no}/accept",
{
params: { path: { project_id: projectId, chapter_no: chapterNo } },
body,
},
);
if (error || !data) {
setStatus("error");
const env = error as ApiErrorEnvelope | undefined;
const code = env?.error?.code;
const missing = env?.error?.details?.missing_conflict_indices ?? [];
if (code === "CONFLICT_UNRESOLVED") {
return { result: null, missingIndices: missing, conflictUnresolved: true };
}
toast("验收失败,请重试(正文未丢失)", "error");
return { result: null, missingIndices: [], conflictUnresolved: false };
}
setResult(data);
setStatus("accepted");
toast("本章已验收", "success");
return { result: data, missingIndices: [], conflictUnresolved: false };
},
[toast],
);
return { status, result, accept };
}