From f43d8f367e601ad909d0b683679388a595c76a93 Mon Sep 17 00:00:00 2001 From: "christian.vidal" Date: Fri, 27 Mar 2026 12:23:35 +0100 Subject: [PATCH] feat: persist edits to Supabase database --- src/App.tsx | 34 ++++++++++++++++++++-- src/lib/supabase.ts | 69 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 src/lib/supabase.ts diff --git a/src/App.tsx b/src/App.tsx index 3fa05bd..9d185bf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { DataCompleteness } from './components/DataCompleteness'; import { MatrixView } from './components/MatrixView'; import { UploadReload } from './components/UploadReload'; import { EditPanel } from './components/EditPanel'; +import { getAllSyncedRows, saveRowToSupabase } from './lib/supabase'; export default function App() { const [appState, setAppState] = useState({ @@ -60,9 +61,22 @@ export default function App() { const data = XLSX.utils.sheet_to_json(ws, { header: 1 }); if (data.length > 0) { + const rawHeaders = data[0]; + const rawRows = data.slice(1); + + // Apply Supabase overrides + console.log("Applying Supabase overrides..."); + const syncedData = await getAllSyncedRows(); + const articleNoIdx = COLUMNS.ARTICLE_NO; + + const processedRows = rawRows.map(row => { + const articleNo = String(row[articleNoIdx]); + return syncedData[articleNo] || row; + }); + setAppState({ - headers: data[0], - data: data.slice(1), + headers: rawHeaders, + data: processedRows, fileName: 'Data-Matrix.xlsx (Cloud Sync)', fileDate: new Date(), hasUnsavedChanges: false @@ -132,7 +146,8 @@ export default function App() { setAppState(prev => ({ ...prev, hasUnsavedChanges: false })); }; - const handleSaveRow = (rowIndex: number, updatedRow: ExcelRow) => { + const handleSaveRow = async (rowIndex: number, updatedRow: ExcelRow) => { + // 1. Update UI state setAppState(prev => { const newData = [...prev.data]; newData[rowIndex] = updatedRow; @@ -143,6 +158,19 @@ export default function App() { }; }); setEditingRowIndex(null); + + // 2. Persist to Supabase + const articleNo = String(updatedRow[COLUMNS.ARTICLE_NO]); + console.log(`Saving article ${articleNo} to Supabase...`); + const success = await saveRowToSupabase(articleNo, updatedRow); + + if (success) { + console.log(`Successfully saved ${articleNo}`); + setAppState(prev => ({ ...prev, hasUnsavedChanges: false })); + } else { + console.error(`Failed to save ${articleNo} to Supabase`); + alert("Error saving to database. Local changes will be lost on refresh if not saved."); + } }; const stats = useMemo(() => { diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts new file mode 100644 index 0000000..812b4be --- /dev/null +++ b/src/lib/supabase.ts @@ -0,0 +1,69 @@ +import { ExcelRow } from '../types'; + +const SUPABASE_URL = 'https://hwithddwaapyhnfwcesj.supabase.co'; +const SUPABASE_KEY = 'sb_publishable_fGXkh0bSrAOqSk2jWKAzSg_NJD9YPCv'; + +export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow) { + try { + const response = await fetch(`${SUPABASE_URL}/rest/v1/products_sync?id=eq.${encodeURIComponent(articleNo)}`, { + method: 'PATCH', + headers: { + 'apikey': SUPABASE_KEY, + 'Authorization': `Bearer ${SUPABASE_KEY}`, + 'Content-Type': 'application/json', + 'Prefer': 'resolution=merge-duplicates' + }, + body: JSON.stringify({ + id: articleNo, + data: rowData, + updated_at: new Date().toISOString() + }) + }); + + if (response.status === 204 || response.ok) { + // If PATCH didn't find the record, try UPSERT + const upsertResponse = await fetch(`${SUPABASE_URL}/rest/v1/products_sync`, { + method: 'POST', + headers: { + 'apikey': SUPABASE_KEY, + 'Authorization': `Bearer ${SUPABASE_KEY}`, + 'Content-Type': 'application/json', + 'Prefer': 'resolution=merge-duplicates' + }, + body: JSON.stringify({ + id: articleNo, + data: rowData, + updated_at: new Date().toISOString() + }) + }); + return upsertResponse.ok; + } + return true; + } catch (error) { + console.error('Error saving to Supabase:', error); + return false; + } +} + +export async function getAllSyncedRows(): Promise> { + try { + const response = await fetch(`${SUPABASE_URL}/rest/v1/products_sync`, { + headers: { + 'apikey': SUPABASE_KEY, + 'Authorization': `Bearer ${SUPABASE_KEY}` + } + }); + + if (!response.ok) return {}; + + const data = await response.json(); + const result: Record = {}; + data.forEach((item: any) => { + result[item.id] = item.data; + }); + return result; + } catch (error) { + console.error('Error fetching from Supabase:', error); + return {}; + } +}