fix: merge history data into rows when products table is empty

This commit is contained in:
Christian Vidal Wolf
2026-04-23 19:07:38 +02:00
parent ee94926d04
commit c1e3960a20
2 changed files with 44 additions and 9 deletions
+15 -9
View File
@@ -9,7 +9,7 @@ import { TopBar } from './components/TopBar';
import { ProductDescriptions } from './components/ProductDescriptions'; import { ProductDescriptions } from './components/ProductDescriptions';
import { MatrixView } from './components/MatrixView'; import { MatrixView } from './components/MatrixView';
import { EditPanel } from './components/EditPanel'; import { EditPanel } from './components/EditPanel';
import { getAllSyncedRows, saveRowToSupabase, resetAllPendingRows, saveHistoryEntry, deleteHistoryEntry } from './lib/supabase'; import { getAllSyncedRows, saveRowToSupabase, resetAllPendingRows, saveHistoryEntry, deleteHistoryEntry, getHistoryDataForMerge } from './lib/supabase';
import { getStoredSession, signOut, type AuthSession } from './lib/auth'; import { getStoredSession, signOut, type AuthSession } from './lib/auth';
import { LoginPage } from './components/LoginPage'; import { LoginPage } from './components/LoginPage';
import { DimensionsView } from './components/DimensionsView'; import { DimensionsView } from './components/DimensionsView';
@@ -134,6 +134,9 @@ 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();
console.log('Fetching history data from Supabase for merge...');
const historyData = await getHistoryDataForMerge();
console.log('Supabase synced rows:', Object.keys(syncedData).length, '| History rows:', Object.keys(historyData).length);
// Extend headers with virtual columns if the Excel is shorter than the saved data. // Extend headers with virtual columns if the Excel is shorter than the saved data.
// COLUMNS hardcoded indices (PRODUCT_TYPE=103, ITEM_TO_LOGISTIC=104, etc.) are used // COLUMNS hardcoded indices (PRODUCT_TYPE=103, ITEM_TO_LOGISTIC=104, etc.) are used
@@ -186,33 +189,36 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
const processedRows = rows.map(row => { const processedRows = rows.map(row => {
const articleNo = String(row[articleNoIdx]); const articleNo = String(row[articleNoIdx]);
const synced = syncedData[articleNo]; const synced = syncedData[articleNo];
const hist = historyData[articleNo];
// Start with fresh Dropbox values (prices from Excel) // Start with fresh Dropbox values (prices from Excel)
let finalRow = [...row]; let finalRow = [...row];
// Merge logic: Prioritize internal control columns and active session edits // Merge logic: Prioritize internal control columns from syncedData or historyData
if (synced && synced.data) { const sourceForInternal = synced?.data || hist;
if (sourceForInternal) {
// 1. ALWAYS restore Internal Control Columns (indices >= 100) // 1. ALWAYS restore Internal Control Columns (indices >= 100)
// These are the "TYPE", "Item to Logistic", "Checking", etc. // These are the "TYPE", "Item to Logistic", "Checking", etc.
// We use hardcoded indices from COLUMNS to ensure they stay at the end. // We use hardcoded indices from COLUMNS to ensure they stay at the end.
Object.values(COLUMNS).forEach(idx => { Object.values(COLUMNS).forEach(idx => {
if (idx >= 100 && synced.data[idx] !== undefined && synced.data[idx] !== null) { if (idx >= 100 && sourceForInternal[idx] !== undefined && sourceForInternal[idx] !== null) {
finalRow[idx] = synced.data[idx]; finalRow[idx] = sourceForInternal[idx];
} }
}); });
// 2. For Master Data (indices < 100, like UVP/SRP), only restore if it's an active edit (pending) // 2. For Master Data (indices < 100, like UVP/SRP), only restore if it's an active edit (pending)
// This protects against the "Column Shift" bug where old saved indices might be wrong. // This protects against the "Column Shift" bug where old saved indices might be wrong.
if (synced.status === 'pending') { if (synced?.status === 'pending') {
editableColumns.forEach(idx => { editableColumns.forEach(idx => {
if (idx < 100 && synced.data[idx] !== undefined && synced.data[idx] !== null) { if (idx < 100 && sourceForInternal[idx] !== undefined && sourceForInternal[idx] !== null) {
finalRow[idx] = synced.data[idx]; finalRow[idx] = sourceForInternal[idx];
} }
}); });
} }
// Update row status in UI if it's not the default 'excel' // Update row status in UI if it's not the default 'excel'
if (synced.status && synced.status !== 'excel') { if (synced?.status && synced.status !== 'excel') {
setRowStatuses(prev => ({ ...prev, [articleNo]: synced.status! })); setRowStatuses(prev => ({ ...prev, [articleNo]: synced.status! }));
} }
} }
+29
View File
@@ -194,6 +194,35 @@ export async function getHistory(): Promise<HistoryEntry[]> {
} }
} }
export async function getHistoryDataForMerge(): Promise<Record<string, ExcelRow>> {
try {
const response = await safeFetch(
`${SUPABASE_URL}/rest/v1/products_history?select=product_id,new_data&order=changed_at.desc`,
{ cache: 'no-store' }
);
if (!response.ok) {
console.error('[getHistoryDataForMerge] Error:', response.status);
return {};
}
const entries: Array<{ product_id: string; new_data: ExcelRow }> = await response.json();
const result: Record<string, ExcelRow> = {};
const seen = new Set<string>();
for (const entry of entries) {
if (seen.has(entry.product_id)) continue;
seen.add(entry.product_id);
if (entry.new_data && entry.new_data.length > 0) {
result[entry.product_id] = entry.new_data;
}
}
return result;
} catch (error: any) {
console.error('[getHistoryDataForMerge] Exception:', error.message);
return {};
}
}
export async function deleteHistoryEntry(id: string): Promise<boolean> { export async function deleteHistoryEntry(id: string): Promise<boolean> {
try { try {
const response = await safeFetch( const response = await safeFetch(