eCourt-note/hooks/useHistory.ts

61 lines
2.3 KiB
TypeScript
Raw Permalink Normal View History

"use client";
import { useCallback, useRef, useState } from "react";
// Undo/Redo (PLAN-FEAT.md §3 F2, D10) — history ฝั่ง client ต่อ session ต่อผู้ใช้
// ไม่เก็บใน DB; push เฉพาะ action ของตัวเองที่สำเร็จเท่านั้น (event ของคนอื่นไม่เกี่ยว)
// ตัว entry เก็บ closure ที่เรียก Server Action + sync state เอง — id ของ annotation
// ที่ "สร้างใหม่" หลัง undo ต้องส่งต่อให้ redo ได้ จึงให้ entry เป็น object ที่แก้ field ได้
export type HistoryEntry = {
undo: () => Promise<void>;
redo: () => Promise<void>;
};
export function useHistory() {
// source of truth เป็น ref — state ที่เปลี่ยนแค่ไว้ trigger re-render ปุ่ม disabled
const undoRef = useRef<HistoryEntry[]>([]);
const redoRef = useRef<HistoryEntry[]>([]);
const [, bump] = useState(0);
/** บันทึก action ที่สำเร็จ — action ใหม่เคลียร์ redo stack เสมอ */
const push = useCallback((entry: HistoryEntry) => {
undoRef.current.push(entry);
redoRef.current = [];
bump((n) => n + 1);
}, []);
const undo = useCallback(async () => {
const entry = undoRef.current.pop();
if (!entry) return;
try {
await entry.undo();
redoRef.current.push(entry);
} catch (err) {
// เช่น undo ของ create แต่ row ถูกลบไปแล้วโดยแท็บอื่น → ข้าม ไม่ crash (§5)
console.error("undo ไม่สำเร็จ — ข้ามรายการนี้", err);
}
bump((n) => n + 1);
}, []);
const redo = useCallback(async () => {
const entry = redoRef.current.pop();
if (!entry) return;
try {
await entry.redo();
undoRef.current.push(entry);
} catch (err) {
console.error("redo ไม่สำเร็จ — ข้ามรายการนี้", err);
}
bump((n) => n + 1);
}, []);
return {
push,
undo,
redo,
canUndo: undoRef.current.length > 0,
canRedo: redoRef.current.length > 0,
};
}