import React from 'react'; import { WeeklyPivotRow } from '../services/dataProcessor'; import { ActiveExperiment } from '../types'; import { Top50Badge } from './Top50Badge'; import { StockBadge } from './StockBadge'; import { BuyBoxWarningBadge } from './BuyBoxWarningBadge'; import { ExperimentBadge } from './ExperimentBadge'; import { VendorStockBadge } from './VendorStockBadge'; import { MetricDetailTooltip } from './MetricDetailTooltip'; interface WeeklyRowProps { row: WeeklyPivotRow; weeks: string[]; onDrillDown?: (sku: string) => void; stockMap?: Map; top50Ranking?: { eu: Map; uk: Map }; top50Mode: 'eu' | 'uk'; sortConfig: { key: string; direction: 'asc' | 'desc'; metric: 'units' | 'spend' | 'revenue' | 'rank' | 'gv'; } | null; renderGrowth: (current: number, previous: number) => React.ReactNode; customerFilters: string[]; vendorStockMap?: Map; velocityMap?: Map; buyBoxLostMap?: Map }>; primaryMetric: 'units' | 'revenue'; experimentMap?: Map; onOpenExperiment?: (experimentId: string) => void; } // Helper to get 4-week baseline prior to the start date const getBaselineMetrics = (row: WeeklyPivotRow, weeks: string[], expStartDate: string) => { const startDate = new Date(expStartDate); const jan1 = new Date(startDate.getFullYear(), 0, 1); const dayOfYear = Math.floor((startDate.getTime() - jan1.getTime()) / 86400000) + 1; const weekNum = Math.ceil((dayOfYear + jan1.getDay()) / 7); const startWeekStr = `${startDate.getFullYear()}-${String(weekNum).padStart(2, '0')}`; const startIdx = weeks.indexOf(startWeekStr); if (startIdx === -1) return null; let u = 0, r = 0, g = 0, weeksCount = 0; for (let i = 1; i <= 4; i++) { const w = weeks[startIdx + i]; if (w) { u += (row.unitsByWeek[w] || 0); r += (row.revenueByWeek[w] || 0); g += (row.gvByWeek?.[w] || 0); weeksCount++; } } if (weeksCount === 0) return null; return { units: u / weeksCount, revenue: r / weeksCount, gv: g / weeksCount, cvr: g > 0 ? (u / g) * 100 : 0 }; }; // Helper to check if a week overlaps with an experiment's date range const isWeekInExperiment = (weekKey: string, exp: ActiveExperiment): boolean => { if (!weekKey || !weekKey.includes('-')) return false; const [yearStr, weekStr] = weekKey.split('-'); const year = parseInt(yearStr); const week = parseInt(weekStr); const weekStart = new Date(year, 0, 1 + (week - 1) * 7); const weekEnd = new Date(weekStart.getTime() + 6 * 86400000); const expStart = new Date(exp.start_date); const expEnd = exp.end_date ? new Date(exp.end_date) : new Date(); return weekStart <= expEnd && weekEnd >= expStart; }; export const WeeklyRow: React.FC = React.memo(({ row, weeks, onDrillDown, stockMap, top50Ranking, top50Mode, sortConfig, renderGrowth, customerFilters, vendorStockMap, velocityMap, buyBoxLostMap, primaryMetric, experimentMap, onOpenExperiment }) => { const ranks: { rank: number; label: string; theme: 'amber' | 'blue' | 'indigo' }[] = []; const asin = row.asin.trim().toUpperCase(); if (top50Ranking) { if (top50Mode === 'eu') { const rank = top50Ranking.eu.get(asin); if (rank) ranks.push({ rank, label: 'EU', theme: 'indigo' }); } else { const rank = top50Ranking.uk.get(asin); if (rank) ranks.push({ rank, label: 'UK', theme: 'blue' }); } } return (
{ranks.map((r, i) => ( ))} onDrillDown?.(row.sku)} className={`text-xs font-black uppercase tracking-tighter truncate max-w-[120px] transition-all ${onDrillDown ? 'text-indigo-400 cursor-pointer hover:text-indigo-300 hover:underline' : 'text-indigo-400/70'}`} title={onDrillDown ? `Click to see Ads detail for ${row.sku}` : ''} > {row.sku || '-'} {row.asin}
{row.title} {stockMap && ( )} {experimentMap && ( )}
{row.line}
{/* Coverage Column */}
{weeks.map((week, idx) => { const val = row.unitsByWeek[week] || 0; const prevVal = row.unitsByWeek[weeks[idx + 1]] || 0; const spend = row.spendByWeek[week] || 0; const prevSpend = row.spendByWeek[weeks[idx + 1]] || 0; const revenue = row.revenueByWeek[week] || 0; const prevRevenue = row.revenueByWeek[weeks[idx + 1]] || 0; const gv = row.gvByWeek?.[week] || 0; const prevGv = row.gvByWeek?.[weeks[idx + 1]] || 0; const [year, weekNum] = week.split('-'); const prevWeekNum = weeks[idx + 1]?.split('-')[1] || '-'; const lastYearWeek = `${parseInt(year) - 1}-${weekNum}`; const yoyUnits = row.unitsByWeek[lastYearWeek] || 0; const yoySpend = row.spendByWeek[lastYearWeek] || 0; const yoyRevenue = row.revenueByWeek[lastYearWeek] || 0; const yoyGv = row.gvByWeek?.[lastYearWeek] || 0; const cvr = gv > 0 ? (val / gv) * 100 : 0; const prevCvr = prevGv > 0 ? (prevVal / prevGv) * 100 : 0; const experimentsInWeek = experimentMap?.get(asin)?.filter(exp => isWeekInExperiment(week, exp)) || []; const hasActiveExperiment = experimentsInWeek.length > 0; let highlightClass = ''; let experimentDelta: number | null = null; let baselineValue: number | null = null; if (hasActiveExperiment) { const primaryExp = experimentsInWeek[0]; if (primaryExp.type === 'pricing') highlightClass = 'bg-amber-500/10 border-t border-amber-500/20'; else if (primaryExp.type === 'advertising') highlightClass = 'bg-rose-500/10 border-t border-rose-500/20'; else if (primaryExp.type === 'content') highlightClass = 'bg-emerald-500/10 border-t border-emerald-500/20'; else highlightClass = 'bg-indigo-500/10 border-t border-indigo-500/20'; const baseline = getBaselineMetrics(row, weeks, primaryExp.start_date); if (baseline) { const myMetric = primaryMetric === 'units' ? val : revenue; const baseMetric = primaryMetric === 'units' ? baseline.units : baseline.revenue; if (baseMetric > 0) { experimentDelta = ((myMetric - baseMetric) / baseMetric) * 100; baselineValue = baseMetric; } } } return ( {primaryMetric === 'units' ? (
0 ? (sortConfig?.key === week && sortConfig.metric === 'units' ? 'text-indigo-400' : 'text-white') : 'text-slate-700'}`}> {val > 0 ? val.toLocaleString('de-DE') : '-'} {val > 0 && renderGrowth(val, prevVal)}
) : ( `€${v.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`} experimentDelta={experimentDelta} baselineValue={baselineValue} >
0 ? (sortConfig?.key === week && sortConfig.metric === 'revenue' ? 'text-amber-400' : 'text-white') : 'text-slate-700'}`}> {revenue > 0 ? `€${revenue.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}` : '-'} {revenue > 0 && renderGrowth(revenue, prevRevenue)}
)}
{spend > 0 && ( `€${v.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`} >
Ads: €{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} {renderGrowth(spend, prevSpend)}
)}
{gv > 0 && (
GV: {gv.toLocaleString('de-DE')} {renderGrowth(gv, prevGv)}
CVR: {cvr.toFixed(1)}% {renderGrowth(cvr, prevCvr)}
)}
); })} ); });