"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import { Loader2, Check, AlertCircle } from "lucide-react"; import { adminFetch } from "@/lib/csrf"; interface SectionEditorProps { sectionKey: string; title: string; defaultData?: Partial; /** Return true if data is valid and can be saved. Blocks auto-save when false. */ validate?: (data: T) => boolean; children: (data: T, update: (data: T) => void) => React.ReactNode; } const DEBOUNCE_MS = 800; export function SectionEditor({ sectionKey, title, defaultData, validate, children, }: SectionEditorProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [status, setStatus] = useState<"idle" | "saving" | "saved" | "error">("idle"); const [error, setError] = useState(""); const timerRef = useRef | null>(null); const initialLoadRef = useRef(true); useEffect(() => { adminFetch(`/api/admin/sections/${sectionKey}`) .then((r) => { if (!r.ok) throw new Error("Failed to load"); return r.json(); }) .then((loaded) => setData(defaultData ? { ...defaultData, ...loaded } as T : loaded)) .catch(() => setError("Не удалось загрузить данные")) .finally(() => setLoading(false)); }, [sectionKey]); const save = useCallback(async (dataToSave: T) => { setStatus("saving"); setError(""); try { const res = await adminFetch(`/api/admin/sections/${sectionKey}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(dataToSave), }); if (!res.ok) throw new Error("Failed to save"); setStatus("saved"); setTimeout(() => setStatus((s) => (s === "saved" ? "idle" : s)), 2000); } catch { setStatus("error"); setError("Ошибка сохранения"); setTimeout(() => setStatus((s) => (s === "error" ? "idle" : s)), 4000); } }, [sectionKey]); // Auto-save with debounce whenever data changes (skip initial load) useEffect(() => { if (!data) return; if (initialLoadRef.current) { initialLoadRef.current = false; return; } if (timerRef.current) clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { if (validate && !validate(data)) return; save(data); }, DEBOUNCE_MS); return () => { if (timerRef.current) clearTimeout(timerRef.current); }; }, [data, save]); if (loading) { return (
Загрузка...
); } if (!data) { return

{error || "Данные не найдены"}

; } return (

{title}

{/* Fixed toast popup */} {(status === "saved" || status === "error") && (
{status === "saved" && <> Сохранено} {status === "error" && <> {error}}
)}
{children(data, setData)}
); }