diff --git a/src/App.tsx b/src/App.tsx index 9e0b4b4..7db7331 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -39,7 +39,7 @@ export default function App() { const [isLoadingDefault, setIsLoadingDefault] = useState(true); const [defaultLoadError, setDefaultLoadError] = useState(null); const [rowStatuses, setRowStatuses] = useState>({}); - const [pendingRows, setPendingRows] = useState>({}); + const [pendingRows, setPendingRows] = useState>({}); const [isSavingAll, setIsSavingAll] = useState(false); useEffect(() => { @@ -241,26 +241,47 @@ export default function App() { }; const handleSaveRow = (rowIndex: number, updatedRow: ExcelRow) => { - // Update local UI state only — no Supabase call here. - // Changes are queued in pendingRows and saved manually via handleSaveAll. + const articleNo = String(updatedRow[COLUMNS.ARTICLE_NO]); + const originalData = appState.data[rowIndex]; // Capture before update setAppState(prev => { const newData = [...prev.data]; newData[rowIndex] = updatedRow; return { ...prev, data: newData, hasUnsavedChanges: true }; }); - const articleNo = String(updatedRow[COLUMNS.ARTICLE_NO]); - setPendingRows(prev => ({ ...prev, [articleNo]: updatedRow })); + setPendingRows(prev => ({ + ...prev, + [articleNo]: { + rowIndex, + // Keep the very first originalData if already pending (re-edit case) + originalData: prev[articleNo]?.originalData ?? originalData, + newData: updatedRow, + articleName: String(updatedRow[COLUMNS.ARTICLE_NAME] || articleNo), + } + })); setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' })); setEditingRowIndex(null); }; + const handleRevertRow = (articleNo: string) => { + const pending = pendingRows[articleNo]; + if (!pending) return; + setAppState(prev => { + const newData = [...prev.data]; + newData[pending.rowIndex] = pending.originalData; + const stillPending = Object.keys(pendingRows).length > 1; + return { ...prev, data: newData, hasUnsavedChanges: stillPending }; + }); + setPendingRows(prev => { const n = { ...prev }; delete n[articleNo]; return n; }); + setRowStatuses(prev => { const n = { ...prev }; delete n[articleNo]; return n; }); + }; + const handleSaveAll = async () => { - const entries = Object.entries(pendingRows) as [string, ExcelRow][]; + const entries = Object.entries(pendingRows) as [string, { rowIndex: number; originalData: ExcelRow; newData: ExcelRow; articleName: string }][]; if (entries.length === 0) return; setIsSavingAll(true); let allSuccess = true; - for (const [articleNo, rowData] of entries) { - const success = await saveRowToSupabase(articleNo, rowData); + for (const [articleNo, { newData }] of entries) { + const success = await saveRowToSupabase(articleNo, newData); if (success) { setRowStatuses(prev => ({ ...prev, [articleNo]: 'saved' })); setPendingRows(prev => { const n = { ...prev }; delete n[articleNo]; return n; }); @@ -343,7 +364,9 @@ export default function App() { undoMessage={undoHistory[0]?.message} undoSteps={undoHistory.length} pendingCount={Object.keys(pendingRows).length} + pendingChanges={Object.fromEntries(Object.entries(pendingRows).map(([k, v]) => [k, { articleName: (v as any).articleName }]))} onSaveAll={handleSaveAll} + onRevertRow={handleRevertRow} isSavingAll={isSavingAll} />
diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index 196f251..3e0b159 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { Download, LogOut, Undo2, CloudUpload, Loader2 } from 'lucide-react'; +import React, { useState, useRef, useEffect } from 'react'; +import { Download, LogOut, Undo2, CloudUpload, Loader2, ChevronDown, RotateCcw } from 'lucide-react'; import { cn } from '../lib/utils'; interface TopBarProps { @@ -14,17 +14,38 @@ interface TopBarProps { undoMessage?: string; undoSteps: number; pendingCount: number; + pendingChanges: Record; onSaveAll: () => Promise; + onRevertRow: (articleNo: string) => void; isSavingAll: boolean; } -export function TopBar({ stats, onExport, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage, undoSteps, pendingCount, onSaveAll, isSavingAll }: TopBarProps) { +export function TopBar({ stats, onExport, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage, undoSteps, pendingCount, pendingChanges, onSaveAll, onRevertRow, isSavingAll }: TopBarProps) { + const [showPending, setShowPending] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + if (!showPending) return; + const handler = (e: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { + setShowPending(false); + } + }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [showPending]); + + // Close dropdown when all changes are saved/reverted + useEffect(() => { + if (pendingCount === 0) setShowPending(false); + }, [pendingCount]); + return (
- Craze Scan
@@ -60,15 +81,61 @@ export function TopBar({ stats, onExport, hasData, hasUnsavedChanges, userEmail,
{pendingCount > 0 && ( - +
+ {/* Split button: Save All + dropdown toggle */} +
+ + +
+ + {/* Dropdown: list of pending changes */} + {showPending && ( +
+
+ Pending changes + {pendingCount} unsaved +
+
+ {Object.entries(pendingChanges).map(([articleNo, { articleName }]) => ( +
+
+
+

{articleNo}

+

{articleName}

+
+ +
+ ))} +
+
+ )} +
)} + {hasData && ( )} - +