Fix save changes looping issue and authorize all Supabase API calls

This commit is contained in:
Christian Vidal Wolf
2026-04-10 12:03:12 +02:00
parent a92e136372
commit ee73b85100
3 changed files with 57 additions and 34 deletions
+31 -10
View File
@@ -111,7 +111,7 @@ export default function App() {
} }
console.log('Fetching synced data from Supabase...'); console.log('Fetching synced data from Supabase...');
const syncedData = await getAllSyncedRows(); const syncedData = await getAllSyncedRows(session?.access_token);
const articleNoIdx = COLUMNS.ARTICLE_NO; const articleNoIdx = COLUMNS.ARTICLE_NO;
const processedRows = rows.map(row => { const processedRows = rows.map(row => {
@@ -267,7 +267,7 @@ export default function App() {
// 3. Post-export: Reset pending statuses in Supabase // 3. Post-export: Reset pending statuses in Supabase
console.log('Resetting pending statuses in Supabase...'); console.log('Resetting pending statuses in Supabase...');
resetAllPendingRows().then(success => { resetAllPendingRows(session?.access_token).then(success => {
if (success) { if (success) {
console.log('Successfully reset all pending statuses'); console.log('Successfully reset all pending statuses');
setRowStatuses({}); // Clear local statuses setRowStatuses({}); // Clear local statuses
@@ -319,27 +319,47 @@ export default function App() {
console.log('[handleSaveAll] No entries to save, returning'); console.log('[handleSaveAll] No entries to save, returning');
return; return;
} }
setIsSavingAll(true); setIsSavingAll(true);
let allSuccess = true; let failedArticles: string[] = [];
const token = session?.access_token;
try {
for (const [articleNo, { newData, originalData, articleName }] of entries) { for (const [articleNo, { newData, originalData, articleName }] of entries) {
console.log('[handleSaveAll] Saving article:', articleNo); console.log('[handleSaveAll] Saving article:', articleNo);
const success = await saveRowToSupabase(articleNo, newData); const success = await saveRowToSupabase(articleNo, newData, token);
console.log('[handleSaveAll] Save result for', articleNo, ':', success); console.log('[handleSaveAll] Save result for', articleNo, ':', success);
if (success) { if (success) {
// Also save to history // Also save to history
await saveHistoryEntry(articleNo, articleName, originalData, newData, session?.user?.email || 'unknown'); await saveHistoryEntry(articleNo, articleName, originalData, newData, session?.user?.email || 'unknown', token);
setRowStatuses(prev => ({ ...prev, [articleNo]: 'saved' })); setRowStatuses(prev => ({ ...prev, [articleNo]: 'saved' }));
setPendingRows(prev => { const n = { ...prev }; delete n[articleNo]; return n; }); setPendingRows(prev => {
const n = { ...prev };
delete n[articleNo];
return n;
});
} else { } else {
setRowStatuses(prev => ({ ...prev, [articleNo]: 'error' })); setRowStatuses(prev => ({ ...prev, [articleNo]: 'error' }));
allSuccess = false; failedArticles.push(articleNo);
} }
} }
console.log('[handleSaveAll] Finished, allSuccess:', allSuccess);
if (allSuccess) setAppState(prev => ({ ...prev, hasUnsavedChanges: false })); console.log('[handleSaveAll] Finished loop. Failed:', failedArticles.length);
if (failedArticles.length > 0) {
alert(`Failed to save ${failedArticles.length} items: ${failedArticles.join(', ')}. Please try again.`);
} else {
setAppState(prev => ({ ...prev, hasUnsavedChanges: false }));
}
} catch (err) {
console.error('[handleSaveAll] Critical error:', err);
alert('A critical error occurred while saving. Please check your connection and try again.');
} finally {
setIsSavingAll(false); setIsSavingAll(false);
console.log('[handleSaveAll] isSavingAll set to false'); console.log('[handleSaveAll] isSavingAll set to false');
}
}; };
const captureState = (message: string) => { const captureState = (message: string) => {
@@ -506,6 +526,7 @@ export default function App() {
<HistoryView <HistoryView
headers={appState.headers} headers={appState.headers}
data={appState.data} data={appState.data}
sessionToken={session?.access_token}
onRevert={async (articleNo, revertedData, historyId) => { onRevert={async (articleNo, revertedData, historyId) => {
// Find the row in appState.data and update it // Find the row in appState.data and update it
const rowIndex = appState.data.findIndex(r => String(r[COLUMNS.ARTICLE_NO]) === articleNo); const rowIndex = appState.data.findIndex(r => String(r[COLUMNS.ARTICLE_NO]) === articleNo);
@@ -528,7 +549,7 @@ export default function App() {
})); }));
// Delete the history entry after revert // Delete the history entry after revert
if (historyId) { if (historyId) {
await deleteHistoryEntry(String(historyId)); await deleteHistoryEntry(String(historyId), session?.access_token);
} }
} }
}} }}
+3 -2
View File
@@ -8,9 +8,10 @@ interface HistoryViewProps {
headers: string[]; headers: string[];
data: ExcelRow[]; data: ExcelRow[];
onRevert: (articleNo: string, oldData: ExcelRow, historyId?: number) => void; onRevert: (articleNo: string, oldData: ExcelRow, historyId?: number) => void;
sessionToken?: string;
} }
export function HistoryView({ headers, data, onRevert }: HistoryViewProps) { export function HistoryView({ headers, data, onRevert, sessionToken }: HistoryViewProps) {
const [history, setHistory] = useState<HistoryEntry[]>([]); const [history, setHistory] = useState<HistoryEntry[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [expandedId, setExpandedId] = useState<string | null>(null); const [expandedId, setExpandedId] = useState<string | null>(null);
@@ -22,7 +23,7 @@ export function HistoryView({ headers, data, onRevert }: HistoryViewProps) {
const loadHistory = async () => { const loadHistory = async () => {
setLoading(true); setLoading(true);
const data = await getHistory(); const data = await getHistory(sessionToken);
setHistory(data); setHistory(data);
setLoading(false); setLoading(false);
}; };
+13 -12
View File
@@ -8,14 +8,14 @@ export interface SyncedRow {
status?: 'pending' | 'synced'; status?: 'pending' | 'synced';
} }
export async function getAllSyncedRows(): Promise<Record<string, SyncedRow>> { export async function getAllSyncedRows(token?: string): Promise<Record<string, SyncedRow>> {
try { try {
const response = await fetch( const response = await fetch(
`${SUPABASE_URL}/rest/v1/products?select=product_id,data,status&order=updated_at.desc`, `${SUPABASE_URL}/rest/v1/products?select=product_id,data,status&order=updated_at.desc`,
{ {
headers: { headers: {
'apikey': SUPABASE_KEY, 'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}` 'Authorization': `Bearer ${token || SUPABASE_KEY}`
} }
} }
); );
@@ -33,7 +33,7 @@ export async function getAllSyncedRows(): Promise<Record<string, SyncedRow>> {
} }
} }
export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow): Promise<boolean> { export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow, token?: string): Promise<boolean> {
try { try {
const response = await fetch( const response = await fetch(
`${SUPABASE_URL}/rest/v1/products`, `${SUPABASE_URL}/rest/v1/products`,
@@ -42,7 +42,7 @@ export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow): P
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'apikey': SUPABASE_KEY, 'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}`, 'Authorization': `Bearer ${token || SUPABASE_KEY}`,
'Prefer': 'resolution=merge-duplicates' 'Prefer': 'resolution=merge-duplicates'
}, },
body: JSON.stringify({ body: JSON.stringify({
@@ -76,7 +76,8 @@ export async function saveHistoryEntry(
articleName: string, articleName: string,
oldData: ExcelRow, oldData: ExcelRow,
newData: ExcelRow, newData: ExcelRow,
changedBy: string changedBy: string,
token?: string
): Promise<boolean> { ): Promise<boolean> {
try { try {
const response = await fetch( const response = await fetch(
@@ -86,7 +87,7 @@ export async function saveHistoryEntry(
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'apikey': SUPABASE_KEY, 'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}`, 'Authorization': `Bearer ${token || SUPABASE_KEY}`,
'Prefer': 'return=minimal' 'Prefer': 'return=minimal'
}, },
body: JSON.stringify({ body: JSON.stringify({
@@ -107,14 +108,14 @@ export async function saveHistoryEntry(
} }
} }
export async function getHistory(): Promise<HistoryEntry[]> { export async function getHistory(token?: string): Promise<HistoryEntry[]> {
try { try {
const response = await fetch( const response = await fetch(
`${SUPABASE_URL}/rest/v1/products_history?select=*&order=changed_at.desc&limit=100`, `${SUPABASE_URL}/rest/v1/products_history?select=*&order=changed_at.desc&limit=100`,
{ {
headers: { headers: {
'apikey': SUPABASE_KEY, 'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}` 'Authorization': `Bearer ${token || SUPABASE_KEY}`
} }
} }
); );
@@ -127,7 +128,7 @@ export async function getHistory(): Promise<HistoryEntry[]> {
} }
} }
export async function deleteHistoryEntry(id: string): Promise<boolean> { export async function deleteHistoryEntry(id: string, token?: string): Promise<boolean> {
try { try {
const response = await fetch( const response = await fetch(
`${SUPABASE_URL}/rest/v1/products_history?id=eq.${encodeURIComponent(id)}`, `${SUPABASE_URL}/rest/v1/products_history?id=eq.${encodeURIComponent(id)}`,
@@ -135,7 +136,7 @@ export async function deleteHistoryEntry(id: string): Promise<boolean> {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'apikey': SUPABASE_KEY, 'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}` 'Authorization': `Bearer ${token || SUPABASE_KEY}`
} }
} }
); );
@@ -147,7 +148,7 @@ export async function deleteHistoryEntry(id: string): Promise<boolean> {
} }
} }
export async function resetAllPendingRows(): Promise<boolean> { export async function resetAllPendingRows(token?: string): Promise<boolean> {
try { try {
const response = await fetch( const response = await fetch(
`${SUPABASE_URL}/rest/v1/products?status=eq.pending`, `${SUPABASE_URL}/rest/v1/products?status=eq.pending`,
@@ -156,7 +157,7 @@ export async function resetAllPendingRows(): Promise<boolean> {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'apikey': SUPABASE_KEY, 'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}`, 'Authorization': `Bearer ${token || SUPABASE_KEY}`,
'Prefer': 'return=minimal' 'Prefer': 'return=minimal'
}, },
body: JSON.stringify({ status: 'synced' }) body: JSON.stringify({ status: 'synced' })