"use client"; import { useState, useEffect, useCallback, useRef } from "react"; import Image from "next/image"; import Link from "next/link"; import { Loader2, Plus, Check, AlertCircle } from "lucide-react"; import { adminFetch } from "@/lib/csrf"; import { InputField } from "../_components/FormField"; import { ArrayEditor } from "../_components/ArrayEditor"; import type { TeamMember } from "@/types/content"; type Member = TeamMember & { id: number }; export default function TeamEditorPage() { const [members, setMembers] = useState([]); const [sectionTitle, setSectionTitle] = useState(""); const [loading, setLoading] = useState(true); const [saveStatus, setSaveStatus] = useState<"idle" | "saved" | "error">("idle"); const titleTimerRef = useRef | null>(null); const titleLoadedRef = useRef(false); useEffect(() => { Promise.all([ adminFetch("/api/admin/team").then((r) => r.json()), adminFetch("/api/admin/sections/team").then((r) => r.json()), ]).then(([membersData, sectionData]) => { setMembers(membersData); setSectionTitle(sectionData.title || ""); titleLoadedRef.current = true; }).finally(() => setLoading(false)); }, []); // Auto-save section title with debounce (skip initial load) const titleChangeCount = useRef(0); const pendingSaveRef = useRef(false); useEffect(() => { if (!titleLoadedRef.current) return; titleChangeCount.current++; if (titleChangeCount.current <= 1) return; pendingSaveRef.current = true; if (titleTimerRef.current) clearTimeout(titleTimerRef.current); titleTimerRef.current = setTimeout(async () => { const res = await adminFetch("/api/admin/sections/team", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: sectionTitle }), }); setSaveStatus(res.ok ? "saved" : "error"); if (res.ok) pendingSaveRef.current = false; setTimeout(() => setSaveStatus("idle"), 2000); }, 800); return () => { if (titleTimerRef.current) clearTimeout(titleTimerRef.current); }; }, [sectionTitle]); // Warn before leaving with unsaved title useEffect(() => { function onBeforeUnload(e: BeforeUnloadEvent) { if (pendingSaveRef.current) e.preventDefault(); } function onLinkClick(e: MouseEvent) { if (!pendingSaveRef.current) return; const link = (e.target as HTMLElement).closest("a"); if (!link || link.target === "_blank") return; const href = link.getAttribute("href"); if (!href || href.startsWith("#") || href.startsWith("/admin/team/")) return; e.preventDefault(); e.stopPropagation(); if (titleTimerRef.current) clearTimeout(titleTimerRef.current); adminFetch("/api/admin/sections/team", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: sectionTitle }), }).finally(() => { window.location.href = href; }); } window.addEventListener("beforeunload", onBeforeUnload); document.addEventListener("click", onLinkClick, true); return () => { window.removeEventListener("beforeunload", onBeforeUnload); document.removeEventListener("click", onLinkClick, true); }; }, [sectionTitle]); const saveOrder = useCallback(async (updated: Member[]) => { const previous = members; setMembers(updated); try { const res = await adminFetch("/api/admin/team/reorder", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: updated.map((m) => m.id) }), }); setSaveStatus(res.ok ? "saved" : "error"); if (!res.ok) setMembers(previous); } catch { setSaveStatus("error"); setMembers(previous); } setTimeout(() => setSaveStatus("idle"), 2000); }, [members]); async function deleteMember(id: number) { try { const res = await adminFetch(`/api/admin/team/${id}`, { method: "DELETE" }); if (!res.ok) throw new Error(); setMembers((prev) => prev.filter((m) => m.id !== id)); } catch { setSaveStatus("error"); setTimeout(() => setSaveStatus("idle"), 3000); } } if (loading) { return (
Загрузка...
); } return (
{/* Toast popup */} {saveStatus !== "idle" && (
{saveStatus === "saved" && <> Сохранено} {saveStatus === "error" && <> Ошибка сохранения}
)}

Команда

Добавить
({ id: 0, name: "", role: "", image: "" })} inline hideAdd getItemTitle={(m) => m.name || "Новый участник"} renderItem={(member) => (
{member.image ? ( {member.name} ) : (
)}

{member.name}

{member.role}

)} />
); }