feat: enable ads metrics at any grouping level (SKU, Line, etc.) in Grid

This commit is contained in:
Christian Vidal Wolf
2026-01-21 11:02:12 +01:00
parent 1c2a0e86aa
commit b6573ebe15
3 changed files with 40 additions and 61 deletions
+12 -51
View File
@@ -2,12 +2,12 @@ import React, { useState, useMemo, useEffect } from 'react';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer
} from 'recharts';
import { SalesRecord, PivotRow, AdsRecord } from '../types';
import { SalesRecord, PivotRow, AdsRecord, CombinedKPIs } from '../types';
import { pivotSalesData, generateCSV, aggregateForTimeSeries, aggregateForComparisonTimeSeries, applyPanEUGrouping } from '../services/dataProcessor';
import { DownloadIcon, FunnelIcon, CloseIcon, ChartIcon, TrendingIcon } from './Icons';
interface DataGridProps {
data: SalesRecord[];
data: SalesRecord[] | CombinedKPIs[];
hasCustomerFilter: boolean;
adsData?: AdsRecord[];
}
@@ -271,59 +271,20 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData =
[selectedDimensions]);
// Transform flat data into Pivot structure
const { rows: basePivotRows, years } = useMemo(() => {
const pivotRows = useMemo(() => {
// Apply Pan-EU grouping when no customer filter is applied
const processedData = applyPanEUGrouping(data, hasCustomerFilter);
const processedData = applyPanEUGrouping(data as SalesRecord[], hasCustomerFilter);
return pivotSalesData(processedData, effectiveDimensions);
// pivotSalesData now handles ads aggregation correctly because it receives CombinedKPIs
const { rows } = pivotSalesData(processedData, effectiveDimensions);
return rows;
}, [data, effectiveDimensions, hasCustomerFilter]);
// Enrich pivot rows with ads data aggregated by ASIN
const pivotRows = useMemo(() => {
if (!adsData || adsData.length === 0) return basePivotRows;
// Aggregate ads by ASIN + Year
const adsAggMap = new Map<string, Map<string, { adSpend: number; attributedSales: number }>>();
adsData.forEach(ad => {
const asinKey = ad.asin.toUpperCase();
const yearKey = ad.year.toString();
if (!adsAggMap.has(asinKey)) {
adsAggMap.set(asinKey, new Map());
}
const yearMap = adsAggMap.get(asinKey)!;
if (!yearMap.has(yearKey)) {
yearMap.set(yearKey, { adSpend: 0, attributedSales: 0 });
}
const yearData = yearMap.get(yearKey)!;
yearData.adSpend += ad.cost;
yearData.attributedSales += ad.attributedSales30d;
});
// Enrich each pivot row with ads data
return basePivotRows.map(row => {
const asinKey = row.asin.toUpperCase();
const yearMap = adsAggMap.get(asinKey);
if (!yearMap) return row;
const adsByYear: Record<string, { adSpend: number; attributedSales: number; acos: number; tacos: number }> = {};
yearMap.forEach((adsYearData, yearKey) => {
const salesForYear = row.totalsByYear[yearKey]?.sellOut || 0;
adsByYear[yearKey] = {
adSpend: adsYearData.adSpend,
attributedSales: adsYearData.attributedSales,
acos: adsYearData.attributedSales > 0 ? (adsYearData.adSpend / adsYearData.attributedSales) * 100 : 0,
tacos: salesForYear > 0 ? (adsYearData.adSpend / salesForYear) * 100 : 0,
};
});
return { ...row, adsByYear };
});
}, [basePivotRows, adsData]);
const { years } = useMemo(() => {
// We still need unique years for columns
const yearsSet = new Set(data.map(d => String((d as any).year)));
return { years: Array.from(yearsSet).sort((a, b) => parseInt(b) - parseInt(a)) };
}, [data]);
// Data for the time series chart, supporting single and multi-year comparison
const { chartData, uniqueYears, isComparisonView, chartTitle } = useMemo(() => {