feat: show ad spend and remove grouping in weekly sales tab

This commit is contained in:
Christian Vidal Wolf
2026-01-21 15:05:59 +01:00
parent ecfa3ee9ec
commit e93da747a3
2 changed files with 60 additions and 62 deletions
+53 -58
View File
@@ -31,14 +31,11 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
}); });
}; };
// Group and Sort rows // Sort rows globally (Ungrouped)
const groupedRows = useMemo(() => { const sortedRows = useMemo(() => {
const groups: { [line: string]: WeeklyPivotRow[] } = {}; const result = [...rows];
// Clone and sort rows based on config
const sortedRows = [...rows];
if (sortConfig) { if (sortConfig) {
sortedRows.sort((a, b) => { result.sort((a, b) => {
const valA = a.unitsByWeek[sortConfig.key] || 0; const valA = a.unitsByWeek[sortConfig.key] || 0;
const valB = b.unitsByWeek[sortConfig.key] || 0; const valB = b.unitsByWeek[sortConfig.key] || 0;
if (sortConfig.direction === 'asc') { if (sortConfig.direction === 'asc') {
@@ -47,25 +44,18 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
return valB - valA; return valB - valA;
}); });
} }
return result;
sortedRows.forEach(row => {
const line = row.line || 'Uncategorized';
if (!groups[line]) groups[line] = [];
groups[line].push(row);
});
// Sort lines alphabetically
return Object.keys(groups).sort().reduce((acc, line) => {
acc[line] = groups[line];
return acc;
}, {} as { [line: string]: WeeklyPivotRow[] });
}, [rows, sortConfig]); }, [rows, sortConfig]);
// Calculate totals per week // Calculate totals per week
const weekTotals = useMemo(() => { const weekTotals = useMemo(() => {
const totals: { [weekKey: string]: number } = {}; const totals: { [weekKey: string]: { units: number, spend: number } } = {};
weeks.forEach(week => { weeks.forEach(week => {
totals[week] = rows.reduce((sum, row) => sum + (row.unitsByWeek[week] || 0), 0); totals[week] = rows.reduce((acc, row) => {
acc.units += (row.unitsByWeek[week] || 0);
acc.spend += (row.spendByWeek[week] || 0);
return acc;
}, { units: 0, spend: 0 });
}); });
return totals; return totals;
}, [rows, weeks]); }, [rows, weeks]);
@@ -76,9 +66,9 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
const isPositive = pct >= 0; const isPositive = pct >= 0;
return ( return (
<div className={`text-[8px] leading-none flex items-center justify-center gap-0.5 mt-0.5 font-bold ${isPositive ? 'text-emerald-400' : 'text-red-400'}`}> <span className={`text-[7px] font-bold ${isPositive ? 'text-emerald-400' : 'text-red-400'}`}>
{isPositive ? '▲' : '▼'}{Math.abs(pct).toFixed(0)}% {isPositive ? '▲' : '▼'}{Math.abs(pct).toFixed(0)}%
</div> </span>
); );
}; };
@@ -92,14 +82,15 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
{weeks.map(week => ( {weeks.map(week => (
<th <th
key={week} key={week}
className={`p-2 text-[9px] font-black uppercase tracking-widest text-center border-r border-white/10 w-24 cursor-pointer hover:bg-white/5 transition-colors select-none ${sortConfig?.key === week ? 'text-indigo-400 bg-white/[0.02]' : 'text-slate-400'}`} className={`p-2 text-[9px] font-black uppercase tracking-widest text-center border-r border-white/10 min-w-[100px] cursor-pointer hover:bg-white/5 transition-colors select-none ${sortConfig?.key === week ? 'text-indigo-400 bg-white/[0.02]' : 'text-slate-400'}`}
onClick={() => handleSort(week)} onClick={() => handleSort(week)}
> >
<div className="flex flex-col items-center gap-1"> <div className="flex flex-col items-center gap-0.5">
<span>{week.split('-')[1]}/{week.split('-')[0].slice(-2)}</span> <span>{week.split('-')[1]}/{week.split('-')[0].slice(-2)}</span>
{sortConfig?.key === week && ( {sortConfig?.key === week && (
<span className="text-[10px]">{sortConfig.direction === 'asc' ? '↑' : '↓'}</span> <span className="text-[10px]">{sortConfig.direction === 'asc' ? '↑' : '↓'}</span>
)} )}
<div className="text-[7px] font-normal text-slate-500 mt-0.5">Units / Spend</div>
</div> </div>
</th> </th>
))} ))}
@@ -109,47 +100,51 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
{weeks.map((week, idx) => ( {weeks.map((week, idx) => (
<th key={week} className="p-2 text-xs font-black text-white text-center border-r border-white/10"> <th key={week} className="p-2 text-xs font-black text-white text-center border-r border-white/10">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<span>{weekTotals[week]?.toLocaleString('de-DE')}</span> <div className="flex items-center gap-1">
{renderGrowth(weekTotals[week], weekTotals[weeks[idx + 1]])} <span>{weekTotals[week].units.toLocaleString('de-DE')}</span>
{renderGrowth(weekTotals[week].units, weekTotals[weeks[idx + 1]]?.units)}
</div>
<div className="text-[9px] text-indigo-400">
{weekTotals[week].spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
</div>
</div> </div>
</th> </th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-white/5"> <tbody className="divide-y divide-white/5">
{Object.entries(groupedRows).map(([line, lineRows]) => ( {sortedRows.map((row) => (
<React.Fragment key={line}> <tr key={row.id} className="hover:bg-white/[0.02] transition-colors group">
{/* Category Header */} <td className="p-2 py-1.5 sticky left-0 z-10 bg-slate-900 md:bg-slate-900/95 backdrop-blur-sm group-hover:bg-slate-800 border-r border-white/10">
<tr className="bg-slate-800/50"> <div className="flex flex-col">
<td colSpan={weeks.length + 1} className="p-1 px-3 text-[10px] font-black text-fuchsia-400 uppercase tracking-widest bg-slate-800/80 border-b border-white/5"> <span className="text-[10px] font-black text-indigo-400 uppercase tracking-tighter truncate w-[180px]">{row.sku}</span>
{line} <span className="text-[9px] text-white/60 truncate w-[180px] leading-tight mb-0.5" title={row.title}>{row.title}</span>
</td> <span className="text-[7px] text-fuchsia-400/70 font-bold uppercase tracking-widest">{row.line}</span>
</tr> </div>
{lineRows.map((row) => ( </td>
<tr key={row.id} className="hover:bg-white/[0.02] transition-colors group"> {weeks.map((week, idx) => {
<td className="p-2 py-1.5 sticky left-0 z-10 bg-slate-900 md:bg-slate-900/95 backdrop-blur-sm group-hover:bg-slate-800 border-r border-white/10"> const val = row.unitsByWeek[week] || 0;
<div className="flex flex-col"> const prevVal = row.unitsByWeek[weeks[idx + 1]] || 0;
<span className="text-[10px] font-black text-indigo-400 uppercase tracking-tighter truncate w-[180px]">{row.sku}</span> const spend = row.spendByWeek[week] || 0;
<span className="text-[9px] text-white/60 truncate w-[180px] leading-tight" title={row.title}>{row.title}</span> return (
<td key={week} className={`p-2 py-1 text-center border-r border-white/5 align-middle ${sortConfig?.key === week ? 'bg-white/[0.01]' : ''}`}>
<div className="flex flex-col items-center justify-center gap-0">
<div className="flex items-center gap-0.5">
<span className={`text-xs font-bold ${val > 0 ? (sortConfig?.key === week ? 'text-indigo-400' : 'text-white') : 'text-slate-700'}`}>
{val > 0 ? val.toLocaleString('de-DE') : '-'}
</span>
{val > 0 && renderGrowth(val, prevVal)}
</div>
{spend > 0 && (
<span className="text-[9px] text-indigo-400/80 font-medium">
{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
</span>
)}
</div> </div>
</td> </td>
{weeks.map((week, idx) => { );
const val = row.unitsByWeek[week] || 0; })}
const prevVal = row.unitsByWeek[weeks[idx + 1]] || 0; </tr>
return (
<td key={week} className={`p-2 py-1.5 text-center border-r border-white/5 align-middle ${sortConfig?.key === week ? 'bg-white/[0.01]' : ''}`}>
<div className="flex flex-col items-center justify-center">
<span className={`text-xs font-bold ${val > 0 ? (sortConfig?.key === week ? 'text-indigo-400' : 'text-white') : 'text-slate-700'}`}>
{val > 0 ? val.toLocaleString('de-DE') : '-'}
</span>
{val > 0 && renderGrowth(val, prevVal)}
</div>
</td>
);
})}
</tr>
))}
</React.Fragment>
))} ))}
</tbody> </tbody>
</table> </table>
+7 -4
View File
@@ -1262,11 +1262,12 @@ export interface WeeklyPivotRow {
line: string; line: string;
customer: string; customer: string;
unitsByWeek: { [weekKey: string]: number }; // Key: "YYYY-WW" unitsByWeek: { [weekKey: string]: number }; // Key: "YYYY-WW"
spendByWeek: { [weekKey: string]: number }; // Key: "YYYY-WW"
} }
export const pivotWeeklySalesData = (data: CombinedKPIs[]): { export const pivotWeeklySalesData = (data: CombinedKPIs[]): {
rows: WeeklyPivotRow[], rows: WeeklyPivotRow[],
weeks: string[] weeks: string[]
} => { } => {
// 1. Identify all unique weeks and sort descending (YYYY-WW) // 1. Identify all unique weeks and sort descending (YYYY-WW)
const weekKeys = new Set<string>(); const weekKeys = new Set<string>();
@@ -1292,7 +1293,8 @@ export const pivotWeeklySalesData = (data: CombinedKPIs[]): {
asin: record.asin || '', asin: record.asin || '',
line: record.line || '', line: record.line || '',
customer: record.customer || record.marketplace || '', customer: record.customer || record.marketplace || '',
unitsByWeek: {} unitsByWeek: {},
spendByWeek: {}
}); });
} }
@@ -1300,6 +1302,7 @@ export const pivotWeeklySalesData = (data: CombinedKPIs[]): {
if (record.week) { if (record.week) {
const weekKey = `${record.year}-${String(record.week).padStart(2, '0')}`; const weekKey = `${record.year}-${String(record.week).padStart(2, '0')}`;
row.unitsByWeek[weekKey] = (row.unitsByWeek[weekKey] || 0) + record.unitsTotal; row.unitsByWeek[weekKey] = (row.unitsByWeek[weekKey] || 0) + record.unitsTotal;
row.spendByWeek[weekKey] = (row.spendByWeek[weekKey] || 0) + (record.cost || 0);
} }
}); });