mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 11:05:23 +02:00
feat: implement dynamic YoY comparison columns and colors for Sell Out and Units in Grid view
This commit is contained in:
+113
-60
@@ -1199,45 +1199,48 @@ const DataGrid: React.FC<DataGridProps> = ({ data, filters, hasCustomerFilter, a
|
||||
<tr>
|
||||
<th className="px-4 py-3 min-w-[80px]">SKU</th>
|
||||
<th className="px-4 py-3 min-w-[280px]">Title</th>
|
||||
{years.map((year, idx) => (
|
||||
<React.Fragment key={year}>
|
||||
<th className="px-4 py-3 text-right whitespace-nowrap">
|
||||
{year} SO {idx === 0 && '↓'}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right whitespace-nowrap">
|
||||
{year} U
|
||||
</th>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<th className="px-4 py-3 text-center">S.O. Δ%</th>
|
||||
<th className="px-4 py-3 text-right">S.O. Δ€</th>
|
||||
{years.map((year, idx) => {
|
||||
const prevYear = years[idx + 1];
|
||||
return (
|
||||
<React.Fragment key={year}>
|
||||
<th className="px-4 py-3 text-right whitespace-nowrap">
|
||||
{year} SO {idx === 0 && '↓'}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right whitespace-nowrap">
|
||||
{year} U
|
||||
</th>
|
||||
{prevYear && (
|
||||
<>
|
||||
<th className="px-4 py-3 text-center whitespace-nowrap text-slate-500 font-bold bg-slate-900/10">
|
||||
Δ% {year}/{prevYear}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right whitespace-nowrap text-slate-500 font-bold bg-slate-900/10">
|
||||
Δ€ {year}/{prevYear}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center whitespace-nowrap text-slate-500 font-bold bg-slate-950/20">
|
||||
U Δ% {year}/{prevYear}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right whitespace-nowrap text-slate-500 font-bold bg-slate-950/20">
|
||||
U Δ {year}/{prevYear}
|
||||
</th>
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-900 text-slate-300">
|
||||
{paginatedRows.map((row) => {
|
||||
const latestYear = years[0] || '2026';
|
||||
const prevYear = years[1] || '2025';
|
||||
// Helper to get color code based on year comparison
|
||||
const getYearCellColor = (year: string) => {
|
||||
const sorted = [...years].sort((a, b) => parseInt(b) - parseInt(a));
|
||||
const index = sorted.indexOf(year);
|
||||
if (index === 0) return 'text-cyan-400'; // Current Year (Cyan)
|
||||
if (index === 1) return 'text-indigo-400'; // Previous Year (Indigo)
|
||||
return 'text-slate-400'; // Reference years
|
||||
};
|
||||
|
||||
const currentData = row.totalsByYear[latestYear];
|
||||
const prevData = row.totalsByYear[prevYear];
|
||||
|
||||
const currentSOVal = currentData?.sellOut || 0;
|
||||
const currentUnitsVal = currentData?.units || 0;
|
||||
|
||||
const prevSOVal = prevData?.sellOut || 0;
|
||||
const prevUnitsVal = prevData?.units || 0;
|
||||
|
||||
// Calculate Growth Values
|
||||
let sellOutGrowthPct = 0;
|
||||
if (prevSOVal > 0) {
|
||||
sellOutGrowthPct = ((currentSOVal - prevSOVal) / prevSOVal) * 100;
|
||||
} else if (currentSOVal > 0) {
|
||||
sellOutGrowthPct = 100;
|
||||
}
|
||||
|
||||
const sellOutDeltaVal = currentSOVal - prevSOVal;
|
||||
|
||||
// Calculate Weeks of Coverage (WOC)
|
||||
const skuClean = row.sku?.replace(/(DE|EN)$/i, '');
|
||||
const internalStockVal = stockMap?.get(skuClean) || 0;
|
||||
const vendorStockVal = (top50Mode === 'uk' ? vendorStockMap?.get(row.asin?.trim().toUpperCase())?.uk : vendorStockMap?.get(row.asin?.trim().toUpperCase())?.eu) || 0;
|
||||
@@ -1319,42 +1322,92 @@ const DataGrid: React.FC<DataGridProps> = ({ data, filters, hasCustomerFilter, a
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Metrics Data */}
|
||||
{years.map((year) => {
|
||||
{/* Metrics Data and Growth Columns */}
|
||||
{years.map((year, idx) => {
|
||||
const yData = row.totalsByYear[year];
|
||||
const prevYear = years[idx + 1];
|
||||
const prevData = prevYear ? row.totalsByYear[prevYear] : null;
|
||||
|
||||
const currentSOVal = yData?.sellOut || 0;
|
||||
const currentUnitsVal = yData?.units || 0;
|
||||
const prevSOVal = prevData?.sellOut || 0;
|
||||
const prevUnitsVal = prevData?.units || 0;
|
||||
|
||||
// Sell Out Growth
|
||||
let sellOutGrowthPct = 0;
|
||||
if (prevSOVal > 0) {
|
||||
sellOutGrowthPct = ((currentSOVal - prevSOVal) / prevSOVal) * 100;
|
||||
} else if (currentSOVal > 0) {
|
||||
sellOutGrowthPct = 100;
|
||||
}
|
||||
const sellOutDeltaVal = currentSOVal - prevSOVal;
|
||||
|
||||
// Units Growth
|
||||
let unitsGrowthPct = 0;
|
||||
if (prevUnitsVal > 0) {
|
||||
unitsGrowthPct = ((currentUnitsVal - prevUnitsVal) / prevUnitsVal) * 100;
|
||||
} else if (currentUnitsVal > 0) {
|
||||
unitsGrowthPct = 100;
|
||||
}
|
||||
const unitsDeltaVal = currentUnitsVal - prevUnitsVal;
|
||||
|
||||
const cellColor = getYearCellColor(year);
|
||||
|
||||
return (
|
||||
<React.Fragment key={year}>
|
||||
<td className="px-4 py-4 text-right font-semibold text-slate-100 text-xs align-top">
|
||||
<td className={`px-4 py-4 text-right font-semibold text-xs align-top ${cellColor}`}>
|
||||
{yData ? `€${yData.sellOut.toLocaleString('de-DE', { maximumFractionDigits: 0 })}` : '-'}
|
||||
</td>
|
||||
<td className="px-4 py-4 text-right font-medium text-slate-300 text-xs align-top">
|
||||
<td className={`px-4 py-4 text-right font-medium text-xs align-top ${cellColor}`}>
|
||||
{yData ? yData.units.toLocaleString('de-DE') : '-'}
|
||||
</td>
|
||||
{prevYear && (
|
||||
<>
|
||||
{/* Sell Out Growth Pct */}
|
||||
<td className="px-4 py-4 align-top text-center bg-slate-900/10">
|
||||
{sellOutGrowthPct !== 0 ? (
|
||||
<span className={`inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[9px] font-bold ${sellOutGrowthPct > 0 ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' : 'bg-rose-500/10 text-rose-400 border border-rose-500/20'}`}>
|
||||
{sellOutGrowthPct > 0 ? '▲' : '▼'} {Math.round(Math.abs(sellOutGrowthPct))}%
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-600">-</span>
|
||||
)}
|
||||
</td>
|
||||
{/* Sell Out Delta Val */}
|
||||
<td className={`px-4 py-4 text-right align-top font-bold text-xs bg-slate-900/10 ${sellOutDeltaVal >= 0 ? 'text-emerald-400' : 'text-rose-500'}`}>
|
||||
{sellOutDeltaVal !== 0 ? (
|
||||
<span>
|
||||
{sellOutDeltaVal > 0 ? '+' : ''}€{sellOutDeltaVal.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-600">-</span>
|
||||
)}
|
||||
</td>
|
||||
{/* Units Growth Pct */}
|
||||
<td className="px-4 py-4 align-top text-center bg-slate-950/20">
|
||||
{unitsGrowthPct !== 0 ? (
|
||||
<span className={`inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[9px] font-bold ${unitsGrowthPct > 0 ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' : 'bg-rose-500/10 text-rose-400 border border-rose-500/20'}`}>
|
||||
{unitsGrowthPct > 0 ? '▲' : '▼'} {Math.round(Math.abs(unitsGrowthPct))}%
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-600">-</span>
|
||||
)}
|
||||
</td>
|
||||
{/* Units Delta Val */}
|
||||
<td className={`px-4 py-4 text-right align-top font-semibold text-xs bg-slate-950/20 ${unitsDeltaVal >= 0 ? 'text-emerald-400' : 'text-rose-500'}`}>
|
||||
{unitsDeltaVal !== 0 ? (
|
||||
<span>
|
||||
{unitsDeltaVal > 0 ? '+' : ''}{unitsDeltaVal.toLocaleString('de-DE')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-600">-</span>
|
||||
)}
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Delta growth % */}
|
||||
<td className="px-4 py-4 align-top text-center">
|
||||
{sellOutGrowthPct !== 0 ? (
|
||||
<span className="inline-flex items-center gap-0.5 px-2 py-0.5 rounded text-[10px] font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
|
||||
▲ {Math.round(sellOutGrowthPct)}%
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-600">-</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
{/* Delta growth € */}
|
||||
<td className="px-4 py-4 text-right align-top font-bold text-emerald-400 text-xs">
|
||||
{sellOutDeltaVal !== 0 ? (
|
||||
<span>
|
||||
{sellOutDeltaVal > 0 ? '+' : ''}€{sellOutDeltaVal.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-slate-600">-</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user