Files
writer-work-flow/apps/web/lib/rules/useRules.ts

99 lines
3.3 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 { RuleView } from "@/lib/api/types";
import { buildRuleCreateRequest, hasUsableContent, type RuleLevel } from "./rules";
export interface UseRules {
items: RuleView[];
// 正在新增(仅冻结新增表单,不影响删除)。
adding: boolean;
// 正在删除的那一条 id仅冻结该行删除按钮整页其余照常可操作
deletingId: string | null;
// 新增一条规则(乐观追加 + 失败回滚 + Toast
add: (
projectId: string,
level: RuleLevel,
content: string,
) => Promise<boolean>;
// 删除一条规则(乐观移除 + 失败回滚 + Toast
remove: (projectId: string, ruleId: string) => Promise<boolean>;
}
// 规则页UX §7列出 + 新增规则。乐观追加,失败回滚(仿 useForeshadow
// add 与 remove 用各自独立的进行中标志,避免删一条就冻结整页。
export function useRules(initial: RuleView[]): UseRules {
const [items, setItems] = useState<RuleView[]>(initial);
const [adding, setAdding] = useState(false);
const [deletingId, setDeletingId] = useState<string | null>(null);
const toast = useToast();
const add = useCallback<UseRules["add"]>(
async (projectId, level, content) => {
if (!hasUsableContent(content)) {
toast("请填写规则正文。", "error");
return false;
}
const snapshot = items;
// 乐观项需一个临时 id删除 handle 之前);服务端返回后被权威行替换。
const optimistic: RuleView = {
id: crypto.randomUUID(),
level,
content: content.trim(),
};
setItems((prev) => [...prev, optimistic]);
setAdding(true);
try {
const { data, error } = await api.POST(
"/projects/{project_id}/rules",
{
params: { path: { project_id: projectId } },
body: buildRuleCreateRequest(level, content),
},
);
if (error || !data) {
setItems(snapshot); // 回滚
toast("新增规则失败,请稍后重试。", "error");
return false;
}
// 用服务端权威行替换乐观行(去掉乐观项、追加返回项)。
setItems((prev) => [...prev.slice(0, snapshot.length), data]);
toast("已新增规则", "success");
return true;
} finally {
setAdding(false);
}
},
[items, toast],
);
const remove = useCallback<UseRules["remove"]>(
async (projectId, ruleId) => {
const snapshot = items;
setItems((prev) => prev.filter((r) => r.id !== ruleId));
setDeletingId(ruleId);
try {
const { error } = await api.DELETE(
"/projects/{project_id}/rules/{rule_id}",
{ params: { path: { project_id: projectId, rule_id: ruleId } } },
);
if (error) {
setItems(snapshot); // 回滚
toast("删除规则失败,请稍后重试。", "error");
return false;
}
toast("已删除规则", "success");
return true;
} finally {
setDeletingId(null);
}
},
[items, toast],
);
return { items, adding, deletingId, add, remove };
}