docuflow / src /components /document /VersionHistoryPanel.tsx
Joedroid's picture
refactor: Modularize frontend App.tsx into custom hooks and components and resolve Vite import.meta types
5ccfcd1
Raw
History Blame Contribute Delete
6.38 kB
import React, { useState } from "react";
import { History, X, RotateCcw } from "lucide-react";
import { Document } from "../../types";
interface VersionHistoryPanelProps {
activeDoc: Document;
editorText: string;
setEditorText: (text: string) => void;
createHistoryCheckpoint: (id: string, label?: string) => void;
setDocuments: React.Dispatch<React.SetStateAction<Document[]>>;
syncDocument: (id: string, text: string, flag: boolean) => Promise<void>;
addLog: (text: string, type: "success" | "info" | "sync" | "error") => void;
setToast: (toast: { show: boolean; message: string; timestamp: string }) => void;
setShowVersionHistory: (show: boolean) => void;
activeDocId: string;
}
export default function VersionHistoryPanel({
activeDoc,
editorText,
setEditorText,
createHistoryCheckpoint,
setDocuments,
syncDocument,
addLog,
setToast,
setShowVersionHistory,
activeDocId
}: VersionHistoryPanelProps) {
const [checkpointLabelInput, setCheckpointLabelInput] = useState("");
const handleSaveCheckpoint = () => {
const val = checkpointLabelInput.trim() || "User Checkpoint";
createHistoryCheckpoint(activeDocId, val);
setCheckpointLabelInput("");
setToast({
show: true,
message: "Manual snapshot saved 💾",
timestamp: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
});
};
const handleRestore = (hist: any) => {
// Create safety backup of current content before swapping
createHistoryCheckpoint(activeDocId, "Pre-Restore State Backup");
// Swap text
setEditorText(hist.content);
// Mark document as edited and sync
setDocuments((prevDocs) =>
prevDocs.map((doc) => {
if (doc.id === activeDocId) {
return {
...doc,
content: hist.content,
isSynced: false,
lastSaved: new Date().toISOString()
};
}
return doc;
})
);
syncDocument(activeDocId, hist.content, false);
setToast({
show: true,
message: `Restored: ${hist.label || 'prior snapshot'}`,
timestamp: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
});
addLog(`Restored "${activeDoc.title}" to state: "${hist.label}". Safety checkpoint captured.`, "success");
};
const historyList = activeDoc.history || [];
return (
<div className="w-[190px] sm:w-[220px] flex-shrink-0 bg-[#0F1115] rounded-lg border border-slate-805 p-2.5 flex flex-col min-h-0 text-left animate-slideLeft z-10">
<div className="flex items-center justify-between border-b border-slate-800 pb-1.5 mb-2 flex-shrink-0">
<span className="text-[9px] uppercase font-mono font-bold tracking-wider text-slate-400 flex items-center gap-1">
<History className="h-3 w-3 text-indigo-400" /> History
</span>
<button
type="button"
onClick={() => setShowVersionHistory(false)}
className="text-slate-500 hover:text-white transition p-0.5 rounded cursor-pointer"
>
<X className="h-3 w-3" />
</button>
</div>
{/* Save Custom Checkpoint */}
<div className="space-y-1 mb-2.5 pb-2.5 border-b border-slate-850 flex-shrink-0">
<input
type="text"
value={checkpointLabelInput}
onChange={(e) => setCheckpointLabelInput(e.target.value)}
placeholder="Label (e.g. Pre-Proofread)"
className="w-full text-[9px] font-mono px-2 py-1 bg-[#15181E] text-slate-205 border border-slate-800 rounded placeholder:text-slate-650 focus:outline-none focus:border-indigo-500"
/>
<button
type="button"
onClick={handleSaveCheckpoint}
className="w-full py-1 bg-indigo-650 hover:bg-indigo-600 text-[9px] text-white font-mono rounded transition duration-150 cursor-pointer text-center font-semibold"
>
+ Save Checkpoint
</button>
</div>
{/* List of past checkpoints */}
<div className="flex-grow overflow-y-auto space-y-1.5 font-mono text-[9px] pr-1 scrollbar-thin">
{historyList.length === 0 ? (
<div className="text-slate-650 text-center py-10 text-[9px] font-mono leading-relaxed">
No snapshots yet.<br/>Your keystrokes are saved automatically.
</div>
) : (
historyList.map((hist) => {
const dateObj = new Date(hist.timestamp);
const dateFormatted = dateObj.toLocaleDateString([], { month: "short", day: "numeric" });
const timeFormatted = dateObj.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
return (
<div
key={hist.id}
className="p-2 rounded bg-[#15181E] border border-slate-800 hover:border-slate-705 transition flex flex-col gap-1 text-left relative duration-155"
>
<div className="flex items-center justify-between gap-1">
<span className="text-[9px] text-indigo-400 font-medium truncate max-w-[110px]" title={hist.label}>
{hist.label || "Snapshot"}
</span>
<span className="text-[8px] text-slate-500 font-mono">{dateFormatted}</span>
</div>
<div className="text-[8px] text-slate-550 leading-none font-mono">
{timeFormatted}
</div>
{/* Action row */}
<div className="mt-1 flex items-center justify-between gap-1">
<span className="text-[8px] text-slate-605 font-mono">
{(hist.content.length / 1024).toFixed(2)} KB
</span>
<button
type="button"
onClick={() => handleRestore(hist)}
className="py-0.5 px-1 bg-[#0F1115] hover:bg-slate-800 text-slate-355 hover:text-white rounded text-[8px] transition border border-slate-800 flex items-center gap-0.5 cursor-pointer font-sans"
title="Restore document to this exact snapshot"
>
<RotateCcw className="h-2 w-2 text-indigo-400" /> Restore
</button>
</div>
</div>
);
})
)}
</div>
</div>
);
}