"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useLocale } from "@/lib/i18n/provider"; import { History, RotateCcw, ChevronDown, GitCompare } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { VersionDiffView, type DiffSnapshot } from "@/components/recipe/version-diff-view"; type VersionSummary = { id: string; version: number; title: string; createdAt: string; }; type SnapshotData = DiffSnapshot & { description?: string | null; baseServings?: number; difficulty?: string | null; prepMins?: number | null; cookMins?: number | null; dietaryTags?: Record; }; type VersionDetail = { id: string; version: number; title: string; createdAt: string; snapshotData: SnapshotData; }; export function VersionHistoryButton({ recipeId, currentSnapshot, }: { recipeId: string; currentSnapshot: DiffSnapshot; }) { const t = useTranslations("recipe"); const tForm = useTranslations("recipeForm"); const { locale } = useLocale(); const router = useRouter(); function formatDate(dateStr: string): string { return new Date(dateStr).toLocaleDateString(locale, { month: "short", day: "numeric", year: "numeric" }); } const [open, setOpen] = useState(false); const [versions, setVersions] = useState([]); const [loading, setLoading] = useState(false); const [expandedId, setExpandedId] = useState(null); const [expandedData, setExpandedData] = useState>({}); const [restoringId, setRestoringId] = useState(null); const [comparingId, setComparingId] = useState(null); async function handleOpen(isOpen: boolean) { setOpen(isOpen); if (isOpen) { setLoading(true); try { const res = await fetch(`/api/v1/recipes/${recipeId}/versions`); if (res.ok) { const data = await res.json() as VersionSummary[]; setVersions(data); } } finally { setLoading(false); } } } async function ensureDetail(versionId: string): Promise { if (expandedData[versionId]) return expandedData[versionId]!; const res = await fetch(`/api/v1/recipes/${recipeId}/versions/${versionId}`); if (!res.ok) return null; const data = await res.json() as VersionDetail; setExpandedData((prev) => ({ ...prev, [versionId]: data })); return data; } async function handleExpand(version: VersionSummary) { if (expandedId === version.id) { setExpandedId(null); return; } setExpandedId(version.id); await ensureDetail(version.id); } async function handleCompare(version: VersionSummary) { await ensureDetail(version.id); setComparingId(version.id); } async function handleRestore(version: VersionSummary) { setRestoringId(version.id); try { const res = await fetch(`/api/v1/recipes/${recipeId}/versions/${version.id}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "restore" }), }); if (res.ok) { toast.success(t("versionRestored", { version: version.version })); setOpen(false); router.refresh(); } else { toast.error(t("historyRestoreFailed")); } } finally { setRestoringId(null); } } return ( <> handleOpen(true)} aria-label={t("historyTooltip")}> } /> {t("historyTooltip")} {t("versionHistoryTitle")}
{loading && (

{t("versionsLoading")}

)} {!loading && versions.length === 0 && (

{t("versionsEmpty")}

)} {versions.map((v) => { const isExpanded = expandedId === v.id; const detail = expandedData[v.id]; return (
{isExpanded && (
{!detail ? (

{t("versionDetailLoading")}

) : (

{t("versionDetailSummary", { ingredients: detail.snapshotData.ingredients.length, steps: detail.snapshotData.steps.length, })}

)}
)}
); })}
!isOpen && setComparingId(null)}> {comparingId ? t("versionCompareWith", { version: expandedData[comparingId]?.version ?? "" }) : t("versionCompare")} {comparingId && expandedData[comparingId] && ( )} ); }