feat: persistent multi-step undo system in TopBar

fix: save all changes to Supabase with reliable upsert

- Replace broken PATCH→POST fallback with single atomic upsert (POST +
  Prefer: resolution=merge-duplicates). The old PATCH returned 200 OK
  with empty body for new articles, causing silent data loss on every
  first save per article.
- Fix getAllSyncedRows pagination: add explicit limit=10000 and Range
  header to bypass Supabase's default 1000-row cap.
- Add fetchWithRetry helper (2 retries on 5xx/network errors).
- handleSaveRow now returns Promise<boolean> and closes EditPanel only
  after a confirmed successful save.
- Add 'error' save status: failed rows turn red (border-l-red-500)
  across ProductDescriptions, ArticleDetails, and PricingView.
- EditPanel shows saving spinner, disables buttons while saving, and
  displays inline error message with "Retry Save" on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Vidal Wolf
2026-04-09 08:22:58 +02:00
co-authored by Claude Sonnet 4.6
parent da4673f7c4
commit c544b9b709
6 changed files with 100 additions and 74 deletions
+10 -14
View File
@@ -238,35 +238,31 @@ export default function App() {
setAppState(prev => ({ ...prev, hasUnsavedChanges: false }));
};
const handleSaveRow = async (rowIndex: number, updatedRow: ExcelRow) => {
// 1. Update UI state
const handleSaveRow = async (rowIndex: number, updatedRow: ExcelRow): Promise<boolean> => {
// 1. Optimistically update UI
setAppState(prev => {
const newData = [...prev.data];
newData[rowIndex] = updatedRow;
return {
...prev,
data: newData,
hasUnsavedChanges: true
};
return { ...prev, data: newData, hasUnsavedChanges: true };
});
setEditingRowIndex(null);
// 2. Persist to Supabase
const articleNo = String(updatedRow[COLUMNS.ARTICLE_NO]);
console.log(`Saving article ${articleNo} to Supabase...`);
// Update local status to pending
setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' }));
// 2. Persist to Supabase
const success = await saveRowToSupabase(articleNo, updatedRow);
if (success) {
console.log(`Successfully saved ${articleNo}`);
setRowStatuses(prev => ({ ...prev, [articleNo]: 'saved' }));
setAppState(prev => ({ ...prev, hasUnsavedChanges: false }));
setEditingRowIndex(null); // Close panel only after confirmed save
} else {
console.error(`Failed to save ${articleNo} to Supabase`);
alert("Error saving to database. Local changes will be lost on refresh if not saved.");
setRowStatuses(prev => ({ ...prev, [articleNo]: 'error' }));
// Panel stays open so user can retry
}
return success;
};
const captureState = (message: string) => {