| 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) => { |
| |
| createHistoryCheckpoint(activeDocId, "Pre-Restore State Backup"); |
| |
| setEditorText(hist.content); |
| |
| 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> |
| ); |
| } |
|
|