2026-03-27 12:23:35 +01:00
|
|
|
import { ExcelRow } from '../types';
|
|
|
|
|
|
|
|
|
|
const SUPABASE_URL = 'https://hwithddwaapyhnfwcesj.supabase.co';
|
|
|
|
|
const SUPABASE_KEY = 'sb_publishable_fGXkh0bSrAOqSk2jWKAzSg_NJD9YPCv';
|
|
|
|
|
|
2026-04-09 08:22:58 +02:00
|
|
|
async function fetchWithRetry(
|
|
|
|
|
url: string,
|
|
|
|
|
options: RequestInit,
|
|
|
|
|
retries = 2,
|
|
|
|
|
delayMs = 1000
|
|
|
|
|
): Promise<Response> {
|
|
|
|
|
let lastError: Error | null = null;
|
|
|
|
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(url, options);
|
|
|
|
|
if (res.ok || res.status < 500 || attempt === retries) return res;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
lastError = err as Error;
|
|
|
|
|
if (attempt === retries) throw lastError;
|
|
|
|
|
}
|
|
|
|
|
await new Promise(r => setTimeout(r, delayMs));
|
|
|
|
|
}
|
|
|
|
|
throw lastError ?? new Error('fetch failed');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow): Promise<boolean> {
|
2026-03-27 12:23:35 +01:00
|
|
|
try {
|
2026-04-09 08:22:58 +02:00
|
|
|
// Single upsert: POST with Prefer=resolution=merge-duplicates
|
|
|
|
|
// This handles both INSERT (new article) and UPDATE (existing) atomically.
|
|
|
|
|
// The old PATCH approach silently failed for new articles because Supabase
|
|
|
|
|
// returns 200 OK with an empty body when no rows match — indistinguishable
|
|
|
|
|
// from a successful update.
|
|
|
|
|
const response = await fetchWithRetry(`${SUPABASE_URL}/rest/v1/products_sync`, {
|
|
|
|
|
method: 'POST',
|
2026-03-27 12:23:35 +01:00
|
|
|
headers: {
|
|
|
|
|
'apikey': SUPABASE_KEY,
|
|
|
|
|
'Authorization': `Bearer ${SUPABASE_KEY}`,
|
2026-04-09 08:22:58 +02:00
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Prefer': 'resolution=merge-duplicates'
|
2026-03-27 12:23:35 +01:00
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
id: articleNo,
|
|
|
|
|
data: rowData,
|
2026-04-08 19:30:41 +02:00
|
|
|
status_check: 'pending',
|
2026-03-27 12:23:35 +01:00
|
|
|
updated_at: new Date().toISOString()
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-08 19:30:41 +02:00
|
|
|
if (!response.ok) {
|
2026-04-09 08:22:58 +02:00
|
|
|
const errorData = await response.json().catch(() => ({}));
|
|
|
|
|
console.error('Supabase upsert failed:', response.status, errorData);
|
|
|
|
|
return false;
|
2026-03-27 12:23:35 +01:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error saving to Supabase:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 19:30:41 +02:00
|
|
|
export async function getAllSyncedRows(): Promise<Record<string, { data: ExcelRow, status: string }>> {
|
2026-03-27 12:23:35 +01:00
|
|
|
try {
|
2026-04-09 08:22:58 +02:00
|
|
|
// Explicit limit to avoid Supabase's default 1000-row cap
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
`${SUPABASE_URL}/rest/v1/products_sync?select=id,data,status_check&limit=10000`,
|
|
|
|
|
{
|
|
|
|
|
headers: {
|
|
|
|
|
'apikey': SUPABASE_KEY,
|
|
|
|
|
'Authorization': `Bearer ${SUPABASE_KEY}`,
|
|
|
|
|
'Range': '0-9999'
|
|
|
|
|
}
|
2026-03-27 12:23:35 +01:00
|
|
|
}
|
2026-04-09 08:22:58 +02:00
|
|
|
);
|
2026-03-27 12:23:35 +01:00
|
|
|
|
|
|
|
|
if (!response.ok) return {};
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
2026-04-08 19:30:41 +02:00
|
|
|
const result: Record<string, { data: ExcelRow, status: string }> = {};
|
2026-03-27 12:23:35 +01:00
|
|
|
data.forEach((item: any) => {
|
2026-04-09 08:22:58 +02:00
|
|
|
result[item.id] = {
|
|
|
|
|
data: item.data,
|
|
|
|
|
status: item.status_check || 'original'
|
2026-04-08 19:30:41 +02:00
|
|
|
};
|
2026-03-27 12:23:35 +01:00
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching from Supabase:', error);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-08 19:30:41 +02:00
|
|
|
|
2026-04-09 08:22:58 +02:00
|
|
|
export async function resetAllPendingRows(): Promise<boolean> {
|
2026-04-08 19:30:41 +02:00
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${SUPABASE_URL}/rest/v1/products_sync?status_check=eq.pending`, {
|
|
|
|
|
method: 'PATCH',
|
|
|
|
|
headers: {
|
|
|
|
|
'apikey': SUPABASE_KEY,
|
|
|
|
|
'Authorization': `Bearer ${SUPABASE_KEY}`,
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
status_check: 'original',
|
|
|
|
|
updated_at: new Date().toISOString()
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
return response.ok;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error resetting statuses in Supabase:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-09 09:24:17 +02:00
|
|
|
|
|
|
|
|
export interface HistoryEntry {
|
|
|
|
|
id: string;
|
|
|
|
|
product_id: string;
|
|
|
|
|
article_name: string;
|
|
|
|
|
old_data: ExcelRow;
|
|
|
|
|
new_data: ExcelRow;
|
|
|
|
|
changed_at: string;
|
|
|
|
|
changed_by: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function saveHistoryEntry(
|
|
|
|
|
articleNo: string,
|
|
|
|
|
articleName: string,
|
|
|
|
|
oldData: ExcelRow,
|
|
|
|
|
newData: ExcelRow,
|
|
|
|
|
userEmail: string
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${SUPABASE_URL}/rest/v1/products_history`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'apikey': SUPABASE_KEY,
|
|
|
|
|
'Authorization': `Bearer ${SUPABASE_KEY}`,
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
product_id: articleNo,
|
|
|
|
|
article_name: articleName,
|
|
|
|
|
old_data: oldData,
|
|
|
|
|
new_data: newData,
|
|
|
|
|
changed_at: new Date().toISOString(),
|
|
|
|
|
changed_by: userEmail
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return response.ok;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error saving history to Supabase:', error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getHistory(): Promise<HistoryEntry[]> {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
`${SUPABASE_URL}/rest/v1/products_history?select=*&order=changed_at.desc&limit=100`,
|
|
|
|
|
{
|
|
|
|
|
headers: {
|
|
|
|
|
'apikey': SUPABASE_KEY,
|
|
|
|
|
'Authorization': `Bearer ${SUPABASE_KEY}`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!response.ok) return [];
|
|
|
|
|
return await response.json();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching history from Supabase:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|