"use client"; import { useCallback, useState } from "react"; import { api } from "@/lib/api/client"; import { useToast } from "@/components/Toast"; import type { ForeshadowView } from "@/lib/api/types"; import { buildRegisterRequest, buildTransitionRequest, extractReason, validationReasonMessage, type RegisterInput, type TransitionInput, } from "./board"; export interface UseForeshadow { items: ForeshadowView[]; busy: boolean; register: (input: RegisterInput) => Promise; transition: (code: string, input: TransitionInput) => Promise; } // 伏笔登记/状态变更(乐观更新 + 回滚 + Toast)。 // register:成功后把返回行追加进 items;422 duplicate → 友好提示,不改 items。 // transition:先乐观替换该行 status,失败回滚到旧 items 并 toast(读 details.reason)。 export function useForeshadow(initial: ForeshadowView[]): UseForeshadow { const [items, setItems] = useState(initial); const [busy, setBusy] = useState(false); const toast = useToast(); const register = useCallback( async (input) => { setBusy(true); try { const { data, error } = await api.POST( "/projects/{project_id}/foreshadow", { params: { path: { project_id: input.projectId } }, body: buildRegisterRequest(input), }, ); if (error || !data) { toast(validationReasonMessage(extractReason(error)), "error"); return false; } setItems((prev) => [...prev, data]); toast(`已登记伏笔 ${data.code}`, "success"); return true; } finally { setBusy(false); } }, [toast], ); const transition = useCallback( async (code, input) => { const snapshot = items; // 乐观:本地先改 status(若给了 toStatus)。 if (input.toStatus) { const next = input.toStatus; setItems((prev) => prev.map((it) => (it.code === code ? { ...it, status: next } : it)), ); } setBusy(true); try { const { data, error } = await api.PATCH( "/projects/{project_id}/foreshadow/{code}", { params: { path: { project_id: input.projectId, code }, }, body: buildTransitionRequest(input), }, ); if (error || !data) { setItems(snapshot); // 回滚 toast(validationReasonMessage(extractReason(error)), "error"); return false; } // 用服务端权威行替换(status + progress 都对齐)。 setItems((prev) => prev.map((it) => (it.code === code ? data : it))); toast(`已更新 ${data.code}`, "success"); return true; } finally { setBusy(false); } }, [items, toast], ); return { items, busy, register, transition }; }