Optimize Weekly Sales performance and fix UI hang

This commit is contained in:
Christian Vidal Wolf
2026-01-27 22:42:59 +01:00
parent 747b73a9c2
commit 8be5f1a767
2 changed files with 67 additions and 57 deletions
+40 -40
View File
@@ -1,4 +1,4 @@
import React, { useMemo, useState, useEffect, useCallback } from 'react';
import React, { useMemo, useState, useEffect, useCallback, useRef } from 'react';
import * as XLSX from 'xlsx';
import { CombinedKPIs } from '../types';
import { pivotWeeklySalesData, WeeklyPivotRow } from '../services/dataProcessor';
@@ -196,21 +196,26 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
});
}, []);
// Calculate totals in a SINGLE PASS (O(rows) instead of O(weeks × rows))
// Calculate totals in a SINGLE PASS
const weekTotals = useMemo(() => {
const totals: { [weekKey: string]: { units: number, spend: number, gv: number } } = {};
// Initialize all weeks
weeks.forEach(week => {
totals[week] = { units: 0, spend: 0, gv: 0 };
});
const visibleWeeks = weeks;
// Initialize visible weeks
for (let i = 0; i < visibleWeeks.length; i++) {
totals[visibleWeeks[i]] = { units: 0, spend: 0, gv: 0 };
}
// Single pass through rows
rows.forEach(row => {
weeks.forEach(week => {
totals[week].units += (row.unitsByWeek[week] || 0);
totals[week].spend += (row.spendByWeek[week] || 0);
totals[week].gv += (row.gvByWeek?.[week] || 0);
});
});
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
for (let j = 0; j < visibleWeeks.length; j++) {
const w = visibleWeeks[j];
totals[w].units += (row.unitsByWeek[w] || 0);
totals[w].spend += (row.spendByWeek[w] || 0);
totals[w].gv += (row.gvByWeek?.[w] || 0);
}
}
return totals;
}, [rows, weeks]);
@@ -274,41 +279,36 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
// 2. Sort results
const sortedRows = useMemo(() => {
if (!sortConfig) return filteredRows;
if (!sortConfig || filteredRows.length === 0) return filteredRows;
const result = [...filteredRows];
const metricKey = sortConfig.metric === 'units' ? 'unitsByWeek' :
sortConfig.metric === 'spend' ? 'spendByWeek' : 'gvByWeek';
const weekKey = sortConfig.key;
const direction = sortConfig.direction;
const { key: weekKey, direction, metric } = sortConfig;
result.sort((a, b) => {
if (sortConfig.metric === 'rank') {
if (metric === 'rank') {
if (!top50Ranking) return result;
const rankMap = top50Mode === 'eu' ? top50Ranking.eu : top50Ranking.uk;
result.sort((a, b) => {
const asinA = a.asin.trim().toUpperCase();
const asinB = b.asin.trim().toUpperCase();
let rankA = 999;
let rankB = 999;
if (top50Ranking) {
if (top50Mode === 'eu') {
rankA = top50Ranking.eu.get(asinA) || 999;
rankB = top50Ranking.eu.get(asinB) || 999;
} else {
rankA = top50Ranking.uk.get(asinA) || 999;
rankB = top50Ranking.uk.get(asinB) || 999;
}
}
const rankA = rankMap.get(asinA) || 999;
const rankB = rankMap.get(asinB) || 999;
return direction === 'asc' ? rankA - rankB : rankB - rankA;
}
const valA = a[metricKey][weekKey] || 0;
const valB = b[metricKey][weekKey] || 0;
return direction === 'asc' ? valA - valB : valB - valA;
});
});
} else {
const metricKey = metric === 'units' ? 'unitsByWeek' :
metric === 'spend' ? 'spendByWeek' : 'gvByWeek';
result.sort((a, b) => {
const valA = a[metricKey][weekKey] || 0;
const valB = b[metricKey][weekKey] || 0;
if (valA === valB) return 0;
return direction === 'asc' ? valA - valB : valB - valA;
});
}
return result;
}, [filteredRows, sortConfig]);
}, [filteredRows, sortConfig, top50Ranking, top50Mode]);
// 3. Paginate
const paginatedRows = useMemo(() => {