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
+27 -9
View File
@@ -1013,7 +1013,7 @@ export const getUniqueValues = (data: SalesRecord[], field: keyof SalesRecord):
return Array.from(values).sort();
};
export const pivotSalesData = (data: SalesRecord[], dimensions: string[] = ['title', 'customer', 'line', 'sku']): { rows: PivotRow[], years: string[] } => {
export const pivotSalesData = (data: any[], dimensions: string[] = ['title', 'customer', 'line', 'sku']): { rows: PivotRow[], years: string[] } => {
// 1. Determine all years present in the data for columns
const yearsSet = new Set(data.map(d => d.year));
const years = Array.from(yearsSet).sort((a, b) => b - a).map(String);
@@ -1022,7 +1022,7 @@ export const pivotSalesData = (data: SalesRecord[], dimensions: string[] = ['tit
data.forEach(record => {
// Group by Dynamic Dimensions
const keyParts = dimensions.map(dim => String(record[dim as keyof SalesRecord] || ''));
const keyParts = dimensions.map(dim => String(record[dim] || ''));
const key = keyParts.join('||');
if (!map.has(key)) {
@@ -1039,12 +1039,14 @@ export const pivotSalesData = (data: SalesRecord[], dimensions: string[] = ['tit
monthIndex: i,
byYear: {}
})),
totalsByYear: {}
totalsByYear: {},
adsByYear: {}
});
}
const row = map.get(key)!;
const monthPart = record.month.split('-')[0]; // Handle "Apr-23" -> "Apr"
const monthRaw = record.month || '';
const monthPart = monthRaw.split('-')[0]; // Handle "Apr-23" -> "Apr"
const monthIdx = MONTH_ORDER.indexOf(monthPart);
const yearStr = record.year.toString();
@@ -1052,17 +1054,33 @@ export const pivotSalesData = (data: SalesRecord[], dimensions: string[] = ['tit
if (!row.totalsByYear[yearStr]) {
row.totalsByYear[yearStr] = { sellOut: 0, units: 0 };
}
row.totalsByYear[yearStr].sellOut += record.sellOut;
row.totalsByYear[yearStr].units += record.units;
row.totalsByYear[yearStr].sellOut += (record.sellOut || record.salesTotal || 0);
row.totalsByYear[yearStr].units += (record.units || record.unitsTotal || 0);
// 2. Update Monthly Data
// 2. Update Ads Data (if present in the record)
if (record.cost !== undefined || record.salesAds !== undefined) {
if (!row.adsByYear) row.adsByYear = {};
if (!row.adsByYear[yearStr]) {
row.adsByYear[yearStr] = { adSpend: 0, attributedSales: 0, acos: 0, tacos: 0 };
}
row.adsByYear[yearStr].adSpend += (record.cost || 0);
row.adsByYear[yearStr].attributedSales += (record.salesAds || 0);
// Recalculate ACOS/TACOS at the aggregated level
const ads = row.adsByYear[yearStr];
const sales = row.totalsByYear[yearStr].sellOut;
ads.acos = ads.attributedSales > 0 ? (ads.adSpend / ads.attributedSales) * 100 : 0;
ads.tacos = sales > 0 ? (ads.adSpend / sales) * 100 : 0;
}
// 3. Update Monthly Data
if (monthIdx !== -1) {
const m = row.months[monthIdx];
if (!m.byYear[yearStr]) {
m.byYear[yearStr] = { sellOut: 0, units: 0 };
}
m.byYear[yearStr].sellOut += record.sellOut;
m.byYear[yearStr].units += record.units;
m.byYear[yearStr].sellOut += (record.sellOut || record.salesTotal || 0);
m.byYear[yearStr].units += (record.units || record.unitsTotal || 0);
}
});