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
+35 -35
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;
if (metric === 'rank') {
if (!top50Ranking) return result;
const rankMap = top50Mode === 'eu' ? top50Ranking.eu : top50Ranking.uk;
result.sort((a, b) => {
if (sortConfig.metric === 'rank') {
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;
}
});
} 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(() => {
+24 -14
View File
@@ -1483,20 +1483,31 @@ export const pivotWeeklySalesData = (data: CombinedKPIs[]): {
rows: WeeklyPivotRow[],
weeks: string[]
} => {
// 1. Identify all unique weeks and sort descending (YYYY-WW)
const weekKeys = new Set<string>();
data.forEach(d => {
if (d.week) {
const weekKey = `${d.year}-${String(d.week).padStart(2, '0')}`;
weekKeys.add(weekKey);
}
});
const sortedWeeks = Array.from(weekKeys).sort((a, b) => b.localeCompare(a));
const weekKeysSet = new Set<string>();
const map = new Map<string, WeeklyPivotRow>();
for (let i = 0; i < data.length; i++) {
// Cache week keys to avoid repeated string formatting
// Key: year|week, Value: YYYY-WW
const weekCache = new Map<string, string>();
const getWeekKey = (year: number, week: number) => {
const cacheKey = `${year}|${week}`;
let k = weekCache.get(cacheKey);
if (!k) {
k = `${year}-${String(week).padStart(2, '0')}`;
weekCache.set(cacheKey, k);
}
return k;
};
const len = data.length;
for (let i = 0; i < len; i++) {
const record = data[i];
if (!record.week) continue;
const weekKey = getWeekKey(record.year, record.week);
weekKeysSet.add(weekKey);
const key = record.asin || record.sku || `${record.title}-${record.line}`;
if (!key) continue;
@@ -1516,13 +1527,12 @@ export const pivotWeeklySalesData = (data: CombinedKPIs[]): {
map.set(key, row);
}
if (record.week) {
const weekKey = `${record.year}-${String(record.week).padStart(2, '0')}`;
row.unitsByWeek[weekKey] = (row.unitsByWeek[weekKey] || 0) + (record.unitsTotal || 0);
row.spendByWeek[weekKey] = (row.spendByWeek[weekKey] || 0) + (record.cost || 0);
row.gvByWeek[weekKey] = (row.gvByWeek[weekKey] || 0) + (record.glanceViews || 0);
}
}
const sortedWeeks = Array.from(weekKeysSet).sort((a, b) => b.localeCompare(a));
return {
rows: Array.from(map.values()),