mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 15:15:24 +02:00
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:
co-authored by
Claude Sonnet 4.6
parent
da4673f7c4
commit
c544b9b709
@@ -8,7 +8,7 @@ import { ConfirmModal } from './ConfirmModal';
|
||||
interface EditPanelProps {
|
||||
row: ExcelRow;
|
||||
rowIndex: number;
|
||||
onSave: (rowIndex: number, updatedRow: ExcelRow) => void;
|
||||
onSave: (rowIndex: number, updatedRow: ExcelRow) => Promise<boolean>;
|
||||
onClose: () => void;
|
||||
onCaptureState: (message: string) => void;
|
||||
}
|
||||
@@ -33,6 +33,8 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
|
||||
const [loadingField, setLoadingField] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveError, setSaveError] = useState(false);
|
||||
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
|
||||
const [pendingGeminiField, setPendingGeminiField] = useState<keyof typeof formData | null>(null);
|
||||
const [generatedFields, setGeneratedFields] = useState<Set<string>>(new Set());
|
||||
@@ -134,8 +136,11 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = async () => {
|
||||
setIsConfirmOpen(false);
|
||||
setSaveError(false);
|
||||
setIsSaving(true);
|
||||
|
||||
const hasModifications = Object.keys(formData).some(k => isModified(k as keyof typeof formData));
|
||||
if (hasModifications) {
|
||||
onCaptureState(`Updated product ${row[COLUMNS.ARTICLE_NO]}`);
|
||||
@@ -155,7 +160,13 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
newRow[COLUMNS.MOQ] = formData.moq;
|
||||
newRow[COLUMNS.DETAILS_DE] = formData.detailsDe;
|
||||
newRow[COLUMNS.DETAILS_EN] = formData.detailsEn;
|
||||
onSave(rowIndex, newRow);
|
||||
|
||||
const success = await onSave(rowIndex, newRow);
|
||||
// If save failed, panel stays open — handleSaveRow in App.tsx won't call setEditingRowIndex(null)
|
||||
if (!success) {
|
||||
setSaveError(true);
|
||||
}
|
||||
setIsSaving(false);
|
||||
};
|
||||
|
||||
const DimensionInput = ({ label, field, placeholder }: { label: string, field: keyof typeof formData, placeholder?: string }) => (
|
||||
@@ -304,18 +315,25 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-slate-700 bg-slate-800/50 flex justify-end gap-3">
|
||||
{saveError && (
|
||||
<span className="flex items-center text-sm text-red-400 mr-auto">
|
||||
Error saving — check your connection and retry.
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm font-medium text-slate-300 hover:text-white hover:bg-slate-700 rounded-md transition-colors"
|
||||
disabled={isSaving}
|
||||
className="px-4 py-2 text-sm font-medium text-slate-300 hover:text-white hover:bg-slate-700 rounded-md transition-colors disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsConfirmOpen(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md shadow-lg shadow-blue-900/20 transition-colors"
|
||||
disabled={isSaving}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:opacity-60 text-white text-sm font-medium rounded-md shadow-lg shadow-blue-900/20 transition-colors"
|
||||
>
|
||||
<Save className="w-4 h-4" />
|
||||
Save to Memory
|
||||
{isSaving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
||||
{isSaving ? 'Saving...' : saveError ? 'Retry Save' : 'Save to Memory'}
|
||||
</button>
|
||||
</div>
|
||||
<ConfirmModal
|
||||
|
||||
Reference in New Issue
Block a user