79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
"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(呈现「本次将更新」清单);
|
||
// 失败回滚状态 + toast;409 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 };
|
||
}
|