Enhance Undo system with 5-step history and TopBar badge

This commit is contained in:
Christian Vidal Wolf
2026-03-29 17:50:31 +02:00
parent 5c9d101349
commit c67fcadb48
2 changed files with 27 additions and 13 deletions
+17 -9
View File
@@ -32,7 +32,7 @@ export default function App() {
hasUnsavedChanges: false hasUnsavedChanges: false
}); });
const [activeModule, setActiveModule] = useState<'descriptions' | 'matrix' | 'dimensions'>('descriptions'); const [activeModule, setActiveModule] = useState<'descriptions' | 'matrix' | 'dimensions'>('descriptions');
const [undoState, setUndoState] = useState<{ data: ExcelRow[], message: string } | null>(null); const [undoHistory, setUndoHistory] = useState<{ data: ExcelRow[], message: string }[]>([]);
const [editingRowIndex, setEditingRowIndex] = useState<number | null>(null); const [editingRowIndex, setEditingRowIndex] = useState<number | null>(null);
const [isLoadingDefault, setIsLoadingDefault] = useState(true); const [isLoadingDefault, setIsLoadingDefault] = useState(true);
const [defaultLoadError, setDefaultLoadError] = useState<string | null>(null); const [defaultLoadError, setDefaultLoadError] = useState<string | null>(null);
@@ -248,20 +248,27 @@ export default function App() {
}; };
const captureState = (message: string) => { const captureState = (message: string) => {
setUndoState({ setUndoHistory(prev => {
const newState = {
data: JSON.parse(JSON.stringify(appState.data)), // Deep copy data: JSON.parse(JSON.stringify(appState.data)), // Deep copy
message message
};
// Keep only last 5 steps
const newHistory = [newState, ...prev].slice(0, 5);
return newHistory;
}); });
}; };
const handleUndo = () => { const handleUndo = () => {
if (!undoState) return; if (undoHistory.length === 0) return;
const [lastAction, ...remainingHistory] = undoHistory;
setAppState(prev => ({ setAppState(prev => ({
...prev, ...prev,
data: undoState.data, data: lastAction.data,
hasUnsavedChanges: true hasUnsavedChanges: true
})); }));
setUndoState(null); setUndoHistory(remainingHistory);
}; };
const stats = useMemo(() => { const stats = useMemo(() => {
@@ -305,9 +312,10 @@ export default function App() {
hasUnsavedChanges={appState.hasUnsavedChanges} hasUnsavedChanges={appState.hasUnsavedChanges}
userEmail={session.user.email} userEmail={session.user.email}
onSignOut={handleSignOut} onSignOut={handleSignOut}
canUndo={!!undoState} canUndo={undoHistory.length > 0}
onUndo={handleUndo} onUndo={handleUndo}
undoMessage={undoState?.message} undoMessage={undoHistory[0]?.message}
undoSteps={undoHistory.length}
/> />
<div className="flex flex-1 overflow-hidden"> <div className="flex flex-1 overflow-hidden">
<Sidebar activeModule={activeModule} setActiveModule={setActiveModule} /> <Sidebar activeModule={activeModule} setActiveModule={setActiveModule} />
@@ -360,9 +368,9 @@ export default function App() {
</div> </div>
<UndoToast <UndoToast
undoState={undoState} undoState={undoHistory[0] || null}
onUndo={handleUndo} onUndo={handleUndo}
onClose={() => setUndoState(null)} onClose={() => setUndoHistory([])}
/> />
{editingRowIndex !== null && ( {editingRowIndex !== null && (
+8 -2
View File
@@ -11,9 +11,10 @@ interface TopBarProps {
canUndo: boolean; canUndo: boolean;
onUndo: () => void; onUndo: () => void;
undoMessage?: string; undoMessage?: string;
undoSteps: number;
} }
export function TopBar({ stats, onExport, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage }: TopBarProps) { export function TopBar({ stats, onExport, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage, undoSteps }: TopBarProps) {
return ( return (
<header className="bg-[#020812] border-b border-slate-700/30 h-32 flex items-center justify-between px-10 shrink-0 z-10 shadow-2xl"> <header className="bg-[#020812] border-b border-slate-700/30 h-32 flex items-center justify-between px-10 shrink-0 z-10 shadow-2xl">
<div className="flex items-center -ml-4"> <div className="flex items-center -ml-4">
@@ -73,10 +74,15 @@ export function TopBar({ stats, onExport, hasData, hasUnsavedChanges, userEmail,
<button <button
onClick={onUndo} onClick={onUndo}
title={`Undo: ${undoMessage}`} title={`Undo: ${undoMessage}`}
className="flex items-center gap-2 px-4 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded-md text-sm font-bold transition-all shadow-lg shadow-amber-900/40 animate-in fade-in zoom-in duration-300" className="flex items-center gap-2 px-4 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded-md text-sm font-bold transition-all shadow-lg shadow-amber-900/40 animate-in fade-in zoom-in duration-300 relative group"
> >
<Undo2 className="w-4 h-4" /> <Undo2 className="w-4 h-4" />
BACK / UNDO BACK / UNDO
{undoSteps > 1 && (
<span className="absolute -top-1 -right-1 bg-white text-amber-700 text-[10px] w-4 h-4 rounded-full flex items-center justify-center shadow-md">
{undoSteps}
</span>
)}
</button> </button>
)} )}