feat: revert individual pending changes from TopBar dropdown

- pendingRows now stores originalData + newData per article so changes
  can be individually reverted to their pre-edit state
- TopBar Save button becomes a split button: left saves all, right
  opens a dropdown listing every pending change with article no + name
- Each row in the dropdown has a Revert button that restores the
  original data locally and removes it from the pending queue
- Dropdown closes automatically when all changes are saved/reverted
  or when clicking outside

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Vidal Wolf
2026-04-09 08:58:31 +02:00
co-authored by Claude Sonnet 4.6
parent acc1d06269
commit a7d8dad36e
2 changed files with 115 additions and 25 deletions
+31 -8
View File
@@ -39,7 +39,7 @@ export default function App() {
const [isLoadingDefault, setIsLoadingDefault] = useState(true);
const [defaultLoadError, setDefaultLoadError] = useState<string | null>(null);
const [rowStatuses, setRowStatuses] = useState<Record<string, string>>({});
const [pendingRows, setPendingRows] = useState<Record<string, ExcelRow>>({});
const [pendingRows, setPendingRows] = useState<Record<string, { rowIndex: number; originalData: ExcelRow; newData: ExcelRow; articleName: string }>>({});
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}
/>
<div className="flex flex-1 overflow-hidden">