mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 15:45:24 +02:00
Optimize Weekly Sales performance and fix UI hang
This commit is contained in:
+40
-40
@@ -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 * as XLSX from 'xlsx';
|
||||||
import { CombinedKPIs } from '../types';
|
import { CombinedKPIs } from '../types';
|
||||||
import { pivotWeeklySalesData, WeeklyPivotRow } from '../services/dataProcessor';
|
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 weekTotals = useMemo(() => {
|
||||||
const totals: { [weekKey: string]: { units: number, spend: number, gv: number } } = {};
|
const totals: { [weekKey: string]: { units: number, spend: number, gv: number } } = {};
|
||||||
// Initialize all weeks
|
const visibleWeeks = weeks;
|
||||||
weeks.forEach(week => {
|
|
||||||
totals[week] = { units: 0, spend: 0, gv: 0 };
|
// Initialize visible weeks
|
||||||
});
|
for (let i = 0; i < visibleWeeks.length; i++) {
|
||||||
|
totals[visibleWeeks[i]] = { units: 0, spend: 0, gv: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
// Single pass through rows
|
// Single pass through rows
|
||||||
rows.forEach(row => {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
weeks.forEach(week => {
|
const row = rows[i];
|
||||||
totals[week].units += (row.unitsByWeek[week] || 0);
|
for (let j = 0; j < visibleWeeks.length; j++) {
|
||||||
totals[week].spend += (row.spendByWeek[week] || 0);
|
const w = visibleWeeks[j];
|
||||||
totals[week].gv += (row.gvByWeek?.[week] || 0);
|
totals[w].units += (row.unitsByWeek[w] || 0);
|
||||||
});
|
totals[w].spend += (row.spendByWeek[w] || 0);
|
||||||
});
|
totals[w].gv += (row.gvByWeek?.[w] || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
return totals;
|
return totals;
|
||||||
}, [rows, weeks]);
|
}, [rows, weeks]);
|
||||||
|
|
||||||
@@ -274,41 +279,36 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
|||||||
|
|
||||||
// 2. Sort results
|
// 2. Sort results
|
||||||
const sortedRows = useMemo(() => {
|
const sortedRows = useMemo(() => {
|
||||||
if (!sortConfig) return filteredRows;
|
if (!sortConfig || filteredRows.length === 0) return filteredRows;
|
||||||
|
|
||||||
const result = [...filteredRows];
|
const result = [...filteredRows];
|
||||||
const metricKey = sortConfig.metric === 'units' ? 'unitsByWeek' :
|
const { key: weekKey, direction, metric } = sortConfig;
|
||||||
sortConfig.metric === 'spend' ? 'spendByWeek' : 'gvByWeek';
|
|
||||||
const weekKey = sortConfig.key;
|
|
||||||
const direction = sortConfig.direction;
|
|
||||||
|
|
||||||
result.sort((a, b) => {
|
if (metric === 'rank') {
|
||||||
if (sortConfig.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 asinA = a.asin.trim().toUpperCase();
|
||||||
const asinB = b.asin.trim().toUpperCase();
|
const asinB = b.asin.trim().toUpperCase();
|
||||||
|
const rankA = rankMap.get(asinA) || 999;
|
||||||
let rankA = 999;
|
const rankB = rankMap.get(asinB) || 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return direction === 'asc' ? rankA - rankB : rankB - rankA;
|
return direction === 'asc' ? rankA - rankB : rankB - rankA;
|
||||||
}
|
});
|
||||||
const valA = a[metricKey][weekKey] || 0;
|
} else {
|
||||||
const valB = b[metricKey][weekKey] || 0;
|
const metricKey = metric === 'units' ? 'unitsByWeek' :
|
||||||
return direction === 'asc' ? valA - valB : valB - valA;
|
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;
|
return result;
|
||||||
}, [filteredRows, sortConfig]);
|
}, [filteredRows, sortConfig, top50Ranking, top50Mode]);
|
||||||
|
|
||||||
// 3. Paginate
|
// 3. Paginate
|
||||||
const paginatedRows = useMemo(() => {
|
const paginatedRows = useMemo(() => {
|
||||||
|
|||||||
+27
-17
@@ -1483,20 +1483,31 @@ export const pivotWeeklySalesData = (data: CombinedKPIs[]): {
|
|||||||
rows: WeeklyPivotRow[],
|
rows: WeeklyPivotRow[],
|
||||||
weeks: string[]
|
weeks: string[]
|
||||||
} => {
|
} => {
|
||||||
// 1. Identify all unique weeks and sort descending (YYYY-WW)
|
const weekKeysSet = new Set<string>();
|
||||||
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 map = new Map<string, WeeklyPivotRow>();
|
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];
|
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}`;
|
const key = record.asin || record.sku || `${record.title}-${record.line}`;
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
|
|
||||||
@@ -1516,14 +1527,13 @@ export const pivotWeeklySalesData = (data: CombinedKPIs[]): {
|
|||||||
map.set(key, row);
|
map.set(key, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.week) {
|
row.unitsByWeek[weekKey] = (row.unitsByWeek[weekKey] || 0) + (record.unitsTotal || 0);
|
||||||
const weekKey = `${record.year}-${String(record.week).padStart(2, '0')}`;
|
row.spendByWeek[weekKey] = (row.spendByWeek[weekKey] || 0) + (record.cost || 0);
|
||||||
row.unitsByWeek[weekKey] = (row.unitsByWeek[weekKey] || 0) + (record.unitsTotal || 0);
|
row.gvByWeek[weekKey] = (row.gvByWeek[weekKey] || 0) + (record.glanceViews || 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 {
|
return {
|
||||||
rows: Array.from(map.values()),
|
rows: Array.from(map.values()),
|
||||||
weeks: sortedWeeks
|
weeks: sortedWeeks
|
||||||
|
|||||||
Reference in New Issue
Block a user