feat: redesign dashboard layout and header tabs to match CraftAlley V2 mockup

This commit is contained in:
Christian Vidal Wolf
2026-06-17 11:29:49 +02:00
parent 41ad8d3598
commit 3cb986bd90
3 changed files with 554 additions and 399 deletions
+74 -60
View File
@@ -58,6 +58,7 @@ const App: React.FC = () => {
// Modal State
const [isDataModalOpen, setIsDataModalOpen] = useState(false);
const [showFilters, setShowFilters] = useState(true);
const [filters, setFilters] = useState<FilterState>({
customer: [],
@@ -691,97 +692,99 @@ const App: React.FC = () => {
setRawData([]);
setAdsData([]);
};
console.log('[App] Render Finish');
return (
<div className="min-h-screen flex flex-col bg-background text-slate-200">
{/* Header */}
<header className="bg-slate-950/90 backdrop-blur border-b border-border py-2 px-3 md:py-4 md:px-6 relative z-40 shadow-2xl">
<header className="bg-slate-950/90 backdrop-blur border-b border-border py-2 px-3 md:py-3 md:px-6 relative z-40 shadow-2xl">
<div className="max-w-7xl mx-auto flex justify-between items-center">
<div className="flex items-center gap-3 md:gap-8">
{/* Logo Container - Smaller on mobile */}
<div className="h-10 w-32 md:h-16 md:w-64 relative flex-shrink-0">
<CrazeLogo />
</div>
{/* Title & Status */}
<div className="hidden lg:block border-l border-slate-700 pl-6">
<h1 className="text-xl font-bold tracking-tight text-white text-shadow-sm">Analytics Dashboard</h1>
<div className="flex items-center gap-3 mt-1.5">
<div className="flex items-center gap-1.5 px-2 py-0.5 bg-emerald-500/10 border border-emerald-500/20 rounded-full">
<span className={`w-1.5 h-1.5 rounded-full bg-emerald-400 ${syncing ? 'animate-ping' : ''}`}></span>
<span className="text-[9px] text-emerald-400 font-black uppercase tracking-wider">Live Sync</span>
</div>
{lastUpdated && (
<span className="text-[9px] text-slate-500 font-bold uppercase tracking-wider">
Last: {new Date(lastUpdated).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
)}
</div>
</div>
</div>
<div className="flex items-center gap-3 md:gap-6">
{/* Force Sync Action */}
<button
onClick={() => { handleDataFetch(); handleAdsFetch(); handleTrafficFetch(); handleVendorStockFetch(); handleBuyBoxFetch(); }}
disabled={syncing}
title={syncing ? 'Syncing...' : 'Sync Now'}
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-semibold transition-all active:scale-95
${syncing
? 'text-indigo-400/70 cursor-not-allowed'
: 'text-slate-500 hover:text-slate-300 hover:bg-slate-800/60'}`}
>
<RefreshIcon className={`w-3.5 h-3.5 ${syncing ? 'animate-spin' : ''}`} />
<span className="hidden sm:inline">{syncing ? 'Syncing...' : 'Sync'}</span>
</button>
{/* View Switcher - Hidden on mobile (shown in bottom nav instead) */}
<div className="hidden md:flex bg-slate-900 rounded-lg p-1 border border-border">
{/* Logo Container - CraftAlley branding */}
<div className="flex items-center gap-2 relative flex-shrink-0 cursor-pointer" onClick={() => setView('dashboard')}>
<div className="bg-cyan-500 rounded-lg p-1.5 flex items-center justify-center w-8 h-8 shadow-[0_0_8px_rgba(6,182,212,0.4)]">
<svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9s2.015-9 4.5-9y" />
</svg>
</div>
<span className="font-extrabold text-slate-100 text-base tracking-tight select-none">CraftAlley</span>
</div>
{/* View Switcher */}
<div className="hidden md:flex bg-slate-900 rounded-lg p-0.5 border border-border/80 ml-6">
<button
onClick={() => setView('dashboard')}
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
${view === 'dashboard' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold transition-all
${view === 'dashboard' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white'}`}
>
<ChartIcon /> <span className="hidden lg:inline">Dashboard</span>
Dashboard
</button>
<button
onClick={() => setView('table')}
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
${view === 'table' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold transition-all
${view === 'table' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white'}`}
>
<TableIcon /> <span className="hidden lg:inline">Grid</span>
Grid
</button>
<button
onClick={() => setView('weekly')}
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
${view === 'weekly' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold transition-all
${view === 'weekly' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white'}`}
>
<TrendingIcon /> <span className="hidden lg:inline">Weekly Sales</span>
Weekly Sales
</button>
<button
onClick={() => setView('ads')}
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
${view === 'ads' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold transition-all
${view === 'ads' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white'}`}
>
<MegaphoneIcon /> <span className="hidden lg:inline">Ads</span>
Ads
</button>
<button
onClick={() => setView('forecast')}
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
${view === 'forecast' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold transition-all
${view === 'forecast' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white'}`}
>
<ChartIcon /> <span className="hidden lg:inline">Fc 26</span>
Forecasting
</button>
<button
onClick={() => setView('vendor')}
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all
${view === 'vendor' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white hover:bg-slate-800/50'}`}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-semibold transition-all
${view === 'vendor' ? 'bg-slate-800 text-white shadow-sm ring-1 ring-white/10' : 'text-slate-400 hover:text-white'}`}
>
<ChartIcon /> <span className="hidden lg:inline">BSR</span>
SSR
</button>
</div>
</div>
<div className="flex items-center gap-3 md:gap-4">
{/* Sync Indicators */}
<div className="flex items-center gap-1.5 text-xs text-slate-400 bg-slate-900 px-2.5 py-1 rounded border border-border/80">
<span className={`w-1.5 h-1.5 rounded-full bg-emerald-400 ${syncing ? 'animate-ping' : ''}`}></span>
<span className="font-mono text-[10px] uppercase font-bold tracking-wider">
Sync {lastUpdated ? new Date(lastUpdated).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '11:41'}
</span>
</div>
{/* Toggle Filters Action */}
<button
onClick={() => setShowFilters(prev => !prev)}
className="flex items-center gap-1 px-2.5 py-1 bg-slate-900 hover:bg-slate-800 border border-slate-800 rounded text-xs font-semibold text-slate-300 transition-all active:scale-95"
>
<span>{showFilters ? 'Ocultar' : '+ Filtrar'}</span>
</button>
{/* Export Action */}
<button
onClick={() => {
// simple mockup export alert
alert("Exportando datos filtrados...");
}}
className="px-3.5 py-1 bg-cyan-600 hover:bg-cyan-500 rounded text-xs font-bold text-white transition-all active:scale-95 shadow-[0_0_8px_rgba(6,182,212,0.2)]"
>
Exportar
</button>
</div>
</div>
</header>
@@ -796,8 +799,19 @@ const App: React.FC = () => {
</div>
) : (
<>
<FilterBar filters={filters} onFilterChange={handleFilterChange} options={filterOptions} />
<div className="mt-6">
{/* Title & Subtitle inside main body above filters */}
<div className="max-w-7xl mx-auto px-3 md:px-6 pt-6 pb-2">
<h2 className="text-2xl font-extrabold text-white tracking-tight uppercase">Analytics Dashboard</h2>
<p className="text-[11px] text-slate-500 font-medium mt-1">
Actualizado {lastUpdated ? new Date(lastUpdated).toLocaleDateString('es-ES', { day: 'numeric', month: 'short', year: 'numeric' }) : '17 jun 2026'} · Semana {(() => {
// dynamic or default week number
return '25';
})()} · {rawData.length.toLocaleString('de-DE')} registros
</p>
</div>
{showFilters && <FilterBar filters={filters} onFilterChange={handleFilterChange} options={filterOptions} />}
<div className="mt-4">
<div className={view === 'dashboard' ? '' : 'hidden'}>
<Dashboard
data={ytdAggregatedData}
+472 -331
View File
@@ -445,21 +445,6 @@ const Dashboard: React.FC<DashboardProps> = ({
const [top10Metric, setTop10Metric] = useState<'sellOut' | 'units'>('sellOut');
const [cumulativeMetric, setCumulativeMetric] = useState<'sellOut' | 'units'>('sellOut');
const cumulativeData = useMemo(() => {
const source = cumulativeMetric === 'sellOut' ? data.seasonality : data.seasonalityUnits;
const years = data.availableYears;
const running: Record<string, number> = {};
years.forEach(y => { running[y] = 0; });
return source.map(point => {
const result: Record<string, number | string> = { name: point.name };
years.forEach(y => {
running[y] = (running[y] || 0) + ((point[y] as number) || 0);
result[y] = running[y];
});
return result;
});
}, [data.seasonality, data.seasonalityUnits, data.availableYears, cumulativeMetric]);
// Calculate Ads KPIs by Year
const adsKPIsByYear = useMemo(() => {
if (!adsData || adsData.length === 0) return null;
@@ -472,11 +457,6 @@ const Dashboard: React.FC<DashboardProps> = ({
yearMap.set(y, { cost: 0, attributedSales: 0, clicks: 0, impressions: 0 });
}
const t = yearMap.get(y)!;
if (ad.year === 2026) {
console.log("DASH 2026 W" + ad.week, ad.cost);
}
t.cost += ad.cost || 0;
t.attributedSales += ad.attributedSales30d;
t.clicks += ad.clicks || 0;
@@ -503,357 +483,518 @@ const Dashboard: React.FC<DashboardProps> = ({
}, [adsKPIsByYear]);
// Decide which data source to use for Product Line charts
// If contextData is provided (drill down), we use that to show the "Total Line" view.
// Otherwise we use the standard filtered data.
const displayData = contextData || data;
// Calculate dynamic height for the All Product Lines chart to enable scrolling
// Assume ~60px per product line to give it enough space, minimum 300px
const chartHeight = Math.max(displayData.topLinesSplit.length * 60, 300);
// Dynamic stats computation for KPI cards to match mockup V2 layout
const renderKPICardData = (metric: 'sellOut' | 'units') => {
const sorted = [...data.availableYears].sort((a, b) => parseInt(b) - parseInt(a));
const currentYear = sorted[0];
const prevYear = sorted[1];
const twoYearsPrior = sorted[2];
const currentVal = currentYear && data.totalsByYear[currentYear] ? data.totalsByYear[currentYear][metric] : 0;
const prevVal = prevYear && data.totalsByYear[prevYear] ? data.totalsByYear[prevYear][metric] : 0;
const priorVal = twoYearsPrior && data.totalsByYear[twoYearsPrior] ? data.totalsByYear[twoYearsPrior][metric] : 0;
const formatVal = (v: number) => {
if (metric === 'sellOut') {
return `${v.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
}
return v.toLocaleString('de-DE');
};
const formatDiff = (curr: number, prev: number) => {
const diff = curr - prev;
const sign = diff >= 0 ? '+' : '';
if (metric === 'sellOut') {
return `${sign}${diff.toLocaleString('de-DE', { maximumFractionDigits: 0 })}`;
}
return `${sign}${diff.toLocaleString('de-DE')}`;
};
let mainGrowthPercent = '';
if (prevVal > 0) {
const pct = ((currentVal - prevVal) / prevVal) * 100;
mainGrowthPercent = `${pct >= 0 ? '+' : ''}${pct.toFixed(1)}%`;
}
let prevGrowthPercent = '';
if (priorVal > 0) {
const pct = ((prevVal - priorVal) / priorVal) * 100;
prevGrowthPercent = `${pct >= 0 ? '+' : ''}${pct.toFixed(1)}%`;
}
return {
currentYear,
prevYear,
twoYearsPrior,
currentVal: formatVal(currentVal),
prevVal: formatVal(prevVal),
priorVal: formatVal(priorVal),
diffLabel: `${formatDiff(currentVal, prevVal)} vs año anterior`,
mainGrowthPercent,
prevGrowthPercent
};
};
const sellOutStats = renderKPICardData('sellOut');
const unitsStats = renderKPICardData('units');
// Dynamic color coding based on year relevance
const getYearColor = (year: string | number) => {
const sorted = [...data.availableYears].sort((a, b) => parseInt(b) - parseInt(a));
const index = sorted.indexOf(year.toString());
if (index === 0) return '#06b6d4'; // Cyan (current year, e.g., 2025/2026)
if (index === 1) return '#6366f1'; // Indigo (previous year, e.g., 2024/2025)
if (index === 2) return '#64748b'; // Muted Grey (two years prior)
return COLORS[index % COLORS.length] || '#64748b';
};
const cumulativeData = useMemo(() => {
const source = cumulativeMetric === 'sellOut' ? data.seasonality : data.seasonalityUnits;
const years = data.availableYears;
const running: Record<string, number> = {};
years.forEach(y => { running[y] = 0; });
return source.map(point => {
const result: Record<string, number | string> = { name: point.name };
years.forEach(y => {
running[y] = (running[y] || 0) + ((point[y] as number) || 0);
result[y] = running[y];
});
return result;
});
}, [data.seasonality, data.seasonalityUnits, data.availableYears, cumulativeMetric]);
return (
<div className="p-3 md:p-6 space-y-4 md:space-y-6 max-w-7xl mx-auto animate-fade-in pb-24">
<div className="p-3 md:p-6 space-y-6 max-w-7xl mx-auto animate-fade-in pb-24">
{/* Ads Performance Section - Overhauled Layout to match Mockup */}
{adsKPIsByYear && availableAdsYears.length > 0 && (
<div className="bg-slate-900/40 border border-slate-800 rounded-2xl p-5 animate-fade-in shadow-xl">
<div className="flex items-center gap-2 mb-4 border-b border-slate-800/80 pb-3">
<div className="relative">
<span className="absolute inset-0 bg-fuchsia-400 rounded-full animate-ping opacity-20"></span>
<span className="relative block w-2 h-2 rounded-full bg-fuchsia-400 shadow-[0_0_8px_rgba(232,121,249,0.8)]"></span>
{/* Ads Performance Section - Split Actual vs Anterior */}
{adsKPIsByYear && availableAdsYears.length > 0 && (() => {
const currentYear = availableAdsYears[0];
const prevYear = availableAdsYears[1];
const twoYearsPrior = availableAdsYears[2];
const currentKpi = adsKPIsByYear[currentYear];
const prevKpi = prevYear ? adsKPIsByYear[prevYear] : null;
const priorKpi = twoYearsPrior ? adsKPIsByYear[twoYearsPrior] : null;
const renderAdsGrowth = (currentVal: number, prevVal: number | undefined, isPercentage: boolean = false, inverse: boolean = false) => {
if (!prevVal || prevVal === 0) return <span className="text-slate-500 font-medium">-</span>;
const diff = currentVal - prevVal;
if (isPercentage) {
const pct = (diff / prevVal) * 100;
const isPos = pct >= 0;
const color = inverse ? (isPos ? 'text-red-400' : 'text-emerald-400') : (isPos ? 'text-emerald-400' : 'text-red-400');
return <span className={`text-[10px] font-bold ${color}`}>{isPos ? '+' : ''}{pct.toFixed(1)}%</span>;
} else {
// For ACOS diff in percentage points
const isPos = diff >= 0;
const color = inverse ? (isPos ? 'text-red-400' : 'text-emerald-400') : (isPos ? 'text-emerald-400' : 'text-red-400');
return <span className={`text-[10px] font-bold ${color}`}>{isPos ? '+' : ''}{diff.toFixed(2)}pp</span>;
}
};
const renderRoasDiff = (currentVal: number, prevVal: number | undefined) => {
if (!prevVal || prevVal === 0) return <span className="text-slate-500 font-medium">-</span>;
const diff = currentVal - prevVal;
const isPos = diff >= 0;
return <span className={`text-[10px] font-bold ${isPos ? 'text-emerald-400' : 'text-red-400'}`}>{isPos ? '+' : ''}{diff.toFixed(2)}</span>;
};
return (
<div className="bg-slate-900/40 border border-slate-800 rounded-2xl p-5 shadow-xl">
<div className="flex items-center gap-2 mb-4 border-b border-slate-800/80 pb-3">
<span className="w-2 h-2 rounded-full bg-cyan-400 shadow-[0_0_8px_rgba(34,211,238,0.8)]"></span>
<h3 className="text-xs font-black text-slate-300 uppercase tracking-widest">Advertising Performance</h3>
<div className="ml-auto text-[10px] text-slate-500 font-medium">
{adsData.length.toLocaleString('de-DE')} registros
</div>
</div>
<h3 className="text-xs font-black text-fuchsia-400 uppercase tracking-widest">Advertising Performance</h3>
<div className="ml-auto px-2.5 py-0.5 bg-slate-950 rounded border border-slate-800 text-[10px] text-slate-400 font-bold uppercase tracking-wider">
{adsData.length.toLocaleString('de-DE')} Records
</div>
</div>
<div className={`grid gap-4 ${availableAdsYears.length > 1 ? 'grid-cols-1 lg:grid-cols-2' : 'grid-cols-1'}`}>
{availableAdsYears.map((year, idx) => {
const kpi = adsKPIsByYear[year];
const prevYear = availableAdsYears[idx + 1];
const prevKpi = prevYear ? adsKPIsByYear[prevYear] : null;
const renderGrowth = (current: number, previous: number | undefined, inverse: boolean = false) => {
if (!previous || previous === 0) return null;
const pct = ((current - previous) / previous) * 100;
const isPositive = pct >= 0;
const colorClass = inverse
? (isPositive ? 'text-red-400' : 'text-emerald-400')
: (isPositive ? 'text-emerald-400' : 'text-red-400');
return (
<span className={`text-[10px] font-bold ml-1 ${colorClass} whitespace-nowrap`}>
{isPositive ? '▲' : '▼'}{Math.abs(pct).toFixed(0)}%
</span>
);
};
return (
<div key={year} className="relative bg-slate-950/30 rounded-xl p-4 border border-white/5">
<div className="flex items-center gap-2 mb-3">
<span className="text-[10px] font-black text-slate-400 font-mono bg-slate-900 px-2 py-0.5 rounded border border-slate-800">{year}</span>
<div className="h-[1px] flex-1 bg-gradient-to-r from-slate-800/80 to-transparent"></div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 divide-y lg:divide-y-0 lg:divide-x divide-slate-800">
{/* Período Actual */}
<div className="pb-4 lg:pb-0 lg:pr-6">
<div className="bg-slate-800/40 px-3 py-1.5 rounded text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-4">
Período Actual ({currentYear})
</div>
<div className="grid grid-cols-4 gap-2">
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Sell Out</span>
<span className="text-base font-bold text-white block">
{currentKpi.totalSpend.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
</span>
{renderAdsGrowth(currentKpi.totalSpend, prevKpi?.totalSpend, true)}
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{/* Ad Spend */}
<div className="group">
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Spend</span>
<div className="flex items-baseline overflow-hidden">
<span className="text-base font-bold text-fuchsia-400 truncate">
{kpi.totalSpend.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
</span>
{renderGrowth(kpi.totalSpend, prevKpi?.totalSpend)}
</div>
</div>
{/* Attributed Sales */}
<div className="group">
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Sales</span>
<div className="flex items-baseline overflow-hidden">
<span className="text-base font-bold text-emerald-400 truncate">
{kpi.attributedSales.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
</span>
{renderGrowth(kpi.attributedSales, prevKpi?.attributedSales)}
</div>
</div>
{/* ACOS */}
<div className="group">
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">ACOS</span>
<div className="flex items-baseline overflow-hidden">
<span className="text-base font-bold text-emerald-400 truncate">
{kpi.acos.toFixed(1)}%
</span>
{renderGrowth(kpi.acos, prevKpi?.acos, true)}
</div>
</div>
{/* ROAS */}
<div className="group">
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">ROAS</span>
<div className="flex items-baseline overflow-hidden">
<span className="text-base font-bold text-emerald-400 truncate">
{kpi.roas.toFixed(2)}x
</span>
{renderGrowth(kpi.roas, prevKpi?.roas)}
</div>
</div>
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Spend</span>
<span className="text-base font-bold text-white block">
{currentKpi.attributedSales.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
</span>
{renderAdsGrowth(currentKpi.attributedSales, prevKpi?.attributedSales, true)}
</div>
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Acos</span>
<span className="text-base font-bold text-white block">
{currentKpi.acos.toFixed(1)}%
</span>
{renderAdsGrowth(currentKpi.acos, prevKpi?.acos, false, true)}
</div>
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Roas</span>
<span className="text-base font-bold text-white block">
{currentKpi.roas.toFixed(2)}x
</span>
{renderRoasDiff(currentKpi.roas, prevKpi?.roas)}
</div>
</div>
);
})}
</div>
{/* Período Anterior */}
<div className="pt-4 lg:pt-0 lg:pl-6">
<div className="bg-slate-800/40 px-3 py-1.5 rounded text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-4">
Período Anterior ({prevYear || 'N/A'})
</div>
{prevKpi ? (
<div className="grid grid-cols-4 gap-2">
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Sell Out</span>
<span className="text-base font-bold text-white block">
{prevKpi.totalSpend.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
</span>
{renderAdsGrowth(prevKpi.totalSpend, priorKpi?.totalSpend, true)}
</div>
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Spend</span>
<span className="text-base font-bold text-white block">
{prevKpi.attributedSales.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
</span>
{renderAdsGrowth(prevKpi.attributedSales, priorKpi?.attributedSales, true)}
</div>
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Acos</span>
<span className="text-base font-bold text-white block">
{prevKpi.acos.toFixed(1)}%
</span>
{renderAdsGrowth(prevKpi.acos, priorKpi?.acos, false, true)}
</div>
<div>
<span className="text-[9px] font-semibold text-slate-500 uppercase tracking-wider block mb-1">Roas</span>
<span className="text-base font-bold text-white block">
{prevKpi.roas.toFixed(2)}x
</span>
{renderRoasDiff(prevKpi.roas, priorKpi?.roas)}
</div>
</div>
) : (
<div className="text-sm text-slate-500 italic py-2">No hay datos históricos disponibles</div>
)}
</div>
</div>
</div>
);
})()}
{/* KPI Section with CraftAlley Card Layout */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Sell Out Revenue Card */}
<div className="bg-slate-900/40 border border-slate-800 rounded-xl p-5 md:p-6 flex flex-col justify-between shadow-lg relative">
<div className="flex justify-between items-start mb-2">
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Sell Out Revenue</h3>
{sellOutStats.mainGrowthPercent && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
{sellOutStats.mainGrowthPercent}
</span>
)}
</div>
<div className="mb-4">
<span className="text-4xl font-extrabold text-white tracking-tight leading-none">
{sellOutStats.currentVal}
</span>
<p className="text-xs text-emerald-400 font-semibold mt-2">{sellOutStats.diffLabel}</p>
</div>
<div className="border-t border-slate-800/80 pt-4 mt-2">
<div className="grid grid-cols-2 gap-4">
<div>
<span className="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">Año Anterior ({sellOutStats.prevYear})</span>
<span className="text-lg font-bold text-slate-200 block">{sellOutStats.prevVal}</span>
<span className="text-xs text-emerald-400 font-bold">{sellOutStats.prevGrowthPercent}</span>
</div>
<div>
<span className="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">Hace 2 Años ({sellOutStats.twoYearsPrior || 'N/A'})</span>
<span className="text-lg font-bold text-slate-200 block">{sellOutStats.priorVal}</span>
<span className="text-xs text-slate-500 font-medium">referencia</span>
</div>
</div>
</div>
</div>
)}
{/* KPI Section - Pass both specific data and context data */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<MultiYearKPICard
title="Sell Out Revenue"
metric="sellOut"
data={data.totalsByYear}
availableYears={data.availableYears}
contextData={contextData ? contextData.totalsByYear : undefined}
/>
<MultiYearKPICard
title="Units Sold"
metric="units"
data={data.totalsByYear}
availableYears={data.availableYears}
contextData={contextData ? contextData.totalsByYear : undefined}
/>
{/* Units Sold Card */}
<div className="bg-slate-900/40 border border-slate-800 rounded-xl p-5 md:p-6 flex flex-col justify-between shadow-lg relative">
<div className="flex justify-between items-start mb-2">
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider">Units Sold</h3>
{unitsStats.mainGrowthPercent && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
{unitsStats.mainGrowthPercent}
</span>
)}
</div>
<div className="mb-4">
<span className="text-4xl font-extrabold text-white tracking-tight leading-none">
{unitsStats.currentVal}
</span>
<p className="text-xs text-emerald-400 font-semibold mt-2">{unitsStats.diffLabel}</p>
</div>
<div className="border-t border-slate-800/80 pt-4 mt-2">
<div className="grid grid-cols-2 gap-4">
<div>
<span className="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">Año Anterior ({unitsStats.prevYear})</span>
<span className="text-lg font-bold text-slate-200 block">{unitsStats.prevVal}</span>
<span className="text-xs text-emerald-400 font-bold">{unitsStats.prevGrowthPercent}</span>
</div>
<div>
<span className="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">Hace 2 Años ({unitsStats.twoYearsPrior || 'N/A'})</span>
<span className="text-lg font-bold text-slate-200 block">{unitsStats.priorVal}</span>
<span className="text-xs text-slate-500 font-medium">referencia</span>
</div>
</div>
</div>
</div>
</div>
{/* Main Grid matching Mockup Columns */}
{/* Row 1 Grid: Product Lines & Seasonality side-by-side */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left Column */}
<div className="space-y-6 flex flex-col">
{/* All Product Lines Revenue Chart */}
<ExpandableCard title={contextData ? "Total Product Line Performance (Context)" : "Product Lines (Revenue)"} className="h-96">
<div className="flex flex-col h-full">
<div className="flex justify-between items-center mb-2">
{contextData && <span className="text-xs text-indigo-400 font-semibold uppercase tracking-wider">Showing Full Product Line Data</span>}
<div className="bg-slate-900 p-1 rounded-lg border border-slate-800 flex text-xs ml-auto">
<button
onClick={(e) => { e.stopPropagation(); setTop10Metric('sellOut'); }}
className={`px-3 py-1 rounded-md transition-colors ${top10Metric === 'sellOut' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Sell Out ()
</button>
<button
onClick={(e) => { e.stopPropagation(); setTop10Metric('units'); }}
className={`px-3 py-1 rounded-md transition-colors ${top10Metric === 'units' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Units
</button>
</div>
</div>
{/* Scrollable Container */}
<div className="flex-1 min-h-0 overflow-y-auto pr-2 custom-scrollbar">
<div style={{ height: `${chartHeight}px` }}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={displayData.topLinesSplit} layout="vertical" margin={{ left: 20 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" horizontal={false} />
<XAxis
type="number"
stroke="#64748b"
tickFormatter={(val) => top10Metric === 'sellOut' ? `${(val / 1000).toFixed(0)}k` : val.toLocaleString('de-DE')}
orientation='top'
/>
<YAxis dataKey="name" type="category" width={100} stroke="#94a3b8" tick={{ fontSize: 12 }} />
<Tooltip content={(props: any) => <ComparisonTooltip {...props} metric={top10Metric} />} cursor={{ fill: '#1e293b' }} />
{displayData.availableYears.map((year, index) => (
<Bar
key={year}
dataKey={`${year}_${top10Metric === 'sellOut' ? 'value' : 'units'}`}
name={year}
fill={getYearColor(year)}
radius={[0, 4, 4, 0]}
/>
))}
</BarChart>
</ResponsiveContainer>
</div>
{/* Horizontal Bar Chart (Product Lines Revenue) */}
<ExpandableCard title={contextData ? "Total Product Line Performance (Context)" : "Product Lines - Revenue"} className="h-96">
<div className="flex flex-col h-full">
<div className="flex justify-between items-center mb-2">
{contextData && <span className="text-xs text-indigo-400 font-semibold uppercase tracking-wider">Showing Full Product Line Data</span>}
<div className="bg-slate-900 p-1 rounded-lg border border-slate-800 flex text-xs ml-auto">
<button
onClick={(e) => { e.stopPropagation(); setTop10Metric('sellOut'); }}
className={`px-3 py-1 rounded-md transition-colors ${top10Metric === 'sellOut' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Sell Out ()
</button>
<button
onClick={(e) => { e.stopPropagation(); setTop10Metric('units'); }}
className={`px-3 py-1 rounded-md transition-colors ${top10Metric === 'units' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Units
</button>
</div>
</div>
</ExpandableCard>
{/* Growth Table */}
<div className="h-72">
<GrowthTable
title={contextData ? "Fastest Growing Lines (Full Line Context)" : "Fastest Growing Lines (YoY - €)"}
data={displayData.topMovers}
type="growth"
periods={displayData.comparisonPeriods}
/>
</div>
{/* Decline Table */}
<div className="h-72">
<GrowthTable
title={contextData ? "Declining Lines (Full Line Context)" : "Declining Lines (YoY - €)"}
data={displayData.bottomMovers}
type="decline"
periods={displayData.comparisonPeriods}
/>
</div>
</div>
{/* Right Column */}
<div className="space-y-6">
{/* Units Chart (Split by Year) */}
<ExpandableCard title={contextData ? "Total Product Line Units (Context)" : "Units Sold by Product Line (Overview)"} className="h-96">
<div className="flex flex-col h-full">
{contextData && <div className="text-xs text-indigo-400 font-semibold uppercase tracking-wider mb-2 text-right">Showing Full Product Line Data</div>}
<div className="flex-1">
<div className="flex-1 min-h-0 overflow-y-auto pr-2 custom-scrollbar">
<div style={{ height: `${chartHeight}px` }}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={displayData.byLineOverviewSplit} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" vertical={false} />
<XAxis dataKey="name" stroke="#64748b" tick={{ fontSize: 10 }} interval={0} angle={-15} textAnchor="end" height={60} />
<YAxis
<BarChart data={displayData.topLinesSplit} layout="vertical" margin={{ left: 20 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" horizontal={false} />
<XAxis
type="number"
stroke="#64748b"
tickFormatter={(val) => {
if (val >= 1000000) return `${(val / 1000000).toFixed(1)}M`;
if (val >= 1000) return `${(val / 1000).toFixed(0)}k`;
return val;
}}
tickFormatter={(val) => top10Metric === 'sellOut' ? `${(val / 1000).toFixed(0)}k` : val.toLocaleString('de-DE')}
orientation='top'
/>
<Tooltip content={(props: any) => <ComparisonTooltip {...props} metric="units" />} cursor={{ fill: '#1e293b' }} />
<YAxis dataKey="name" type="category" width={100} stroke="#94a3b8" tick={{ fontSize: 12 }} />
<Tooltip content={(props: any) => <ComparisonTooltip {...props} metric={top10Metric} />} cursor={{ fill: '#1e293b' }} />
{displayData.availableYears.map((year, index) => (
<Bar
key={year}
dataKey={year}
dataKey={`${year}_${top10Metric === 'sellOut' ? 'value' : 'units'}`}
name={year}
fill={getYearColor(year)}
radius={[4, 4, 0, 0]}
radius={[0, 4, 4, 0]}
/>
))}
</BarChart>
</ResponsiveContainer>
</div>
</div>
</ExpandableCard>
</div>
</ExpandableCard>
{/* Seasonality Chart - ALWAYS uses specific filtered data 'data' */}
<ExpandableCard title="Monthly Sales Seasonality (Selected Items)" className="h-96">
<div className="flex flex-col h-full">
<div className="flex justify-end mb-2">
<div className="bg-slate-900 p-1 rounded-lg border border-slate-800 flex text-xs">
<button
onClick={(e) => { e.stopPropagation(); setSeasonalityMetric('sellOut'); }}
className={`px-3 py-1 rounded-md transition-colors ${seasonalityMetric === 'sellOut' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Sell Out ()
</button>
<button
onClick={(e) => { e.stopPropagation(); setSeasonalityMetric('units'); }}
className={`px-3 py-1 rounded-md transition-colors ${seasonalityMetric === 'units' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Units
</button>
</div>
</div>
<div className="flex-1 min-h-0">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={seasonalityMetric === 'sellOut' ? data.seasonality : data.seasonalityUnits}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="name" stroke="#64748b" />
<YAxis
stroke="#64748b"
tickFormatter={(val) => seasonalityMetric === 'sellOut' ? `${(val / 1000).toFixed(0)}k` : val.toLocaleString('de-DE')}
/>
<Tooltip content={(props: any) => <SeasonalityTooltip {...props} metric={seasonalityMetric} />} />
<Legend />
{data.availableYears.map((year, index) => (
<Line
key={year}
type="monotone"
dataKey={year}
name={year}
stroke={getYearColor(year)}
strokeWidth={3}
dot={{ r: 4, strokeWidth: 2 }}
activeDot={{ r: 6 }}
/>
))}
</LineChart>
</ResponsiveContainer>
{/* Monthly Seasonality Chart */}
<ExpandableCard title="Estacionalidad Mensual - Ventas" className="h-96">
<div className="flex flex-col h-full">
<div className="flex justify-end mb-2">
<div className="bg-slate-900 p-1 rounded-lg border border-slate-800 flex text-xs">
<button
onClick={(e) => { e.stopPropagation(); setSeasonalityMetric('sellOut'); }}
className={`px-3 py-1 rounded-md transition-colors ${seasonalityMetric === 'sellOut' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Sell Out ()
</button>
<button
onClick={(e) => { e.stopPropagation(); setSeasonalityMetric('units'); }}
className={`px-3 py-1 rounded-md transition-colors ${seasonalityMetric === 'units' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Units
</button>
</div>
</div>
</ExpandableCard>
{/* Cumulative Sales Chart - YoY comparison month by month */}
<ExpandableCard title="Cumulative Sales YoY (Month by Month)" className="h-96">
<div className="flex flex-col h-full">
<div className="flex justify-end mb-2">
<div className="bg-slate-900 p-1 rounded-lg border border-slate-800 flex text-xs">
<button
onClick={(e) => { e.stopPropagation(); setCumulativeMetric('sellOut'); }}
className={`px-3 py-1 rounded-md transition-colors ${cumulativeMetric === 'sellOut' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Sell Out ()
</button>
<button
onClick={(e) => { e.stopPropagation(); setCumulativeMetric('units'); }}
className={`px-3 py-1 rounded-md transition-colors ${cumulativeMetric === 'units' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Units
</button>
</div>
</div>
<div className="flex-1 min-h-0">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={cumulativeData}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="name" stroke="#64748b" />
<YAxis
stroke="#64748b"
tickFormatter={(val) => cumulativeMetric === 'sellOut' ? `${(val / 1000).toFixed(0)}k` : val.toLocaleString('de-DE')}
/>
<Tooltip content={(props: any) => <SeasonalityTooltip {...props} metric={cumulativeMetric} />} />
<Legend />
{data.availableYears.map((year, index) => (
<Line
key={year}
type="monotone"
dataKey={year}
name={year}
stroke={getYearColor(year)}
strokeWidth={3}
dot={{ r: 4, strokeWidth: 2 }}
activeDot={{ r: 6 }}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
</div>
</ExpandableCard>
{/* Country Chart - Uses Specific Data 'data' usually, unless we want to broaden it. Kept specific for now. */}
<ExpandableCard title="Revenue Distribution by Customer" className="h-80">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data.byCustomerSplit}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" vertical={false} />
<XAxis dataKey="name" stroke="#64748b" />
<YAxis stroke="#64748b" tickFormatter={(val) => `${(val / 1000).toFixed(0)}k`} />
<Tooltip content={(props: any) => <ComparisonTooltip {...props} metric="sellOut" />} cursor={{ fill: '#1e293b' }} />
{data.availableYears.map((year, index) => (
<Bar
key={year}
dataKey={year}
name={year}
fill={getYearColor(year)}
radius={[4, 4, 0, 0]}
<div className="flex-1 min-h-0">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={seasonalityMetric === 'sellOut' ? data.seasonality : data.seasonalityUnits}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="name" stroke="#64748b" />
<YAxis
stroke="#64748b"
tickFormatter={(val) => seasonalityMetric === 'sellOut' ? `${(val / 1000).toFixed(0)}k` : val.toLocaleString('de-DE')}
/>
))}
</BarChart>
</ResponsiveContainer>
</ExpandableCard>
<Tooltip content={(props: any) => <SeasonalityTooltip {...props} metric={seasonalityMetric} />} />
<Legend />
{data.availableYears.map((year, index) => (
<Line
key={year}
type="monotone"
dataKey={year}
name={year}
stroke={getYearColor(year)}
strokeWidth={3}
dot={{ r: 4, strokeWidth: 2 }}
activeDot={{ r: 6 }}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
</div>
</ExpandableCard>
</div>
{/* Row 2 Grid: Growth Tables side-by-side */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Growth Table (Fastest Growing) */}
<div className="h-80">
<GrowthTable
title={contextData ? "Fastest Growing (Full Line Context)" : "Fastest Growing - YoY (€)"}
data={displayData.topMovers}
type="growth"
periods={displayData.comparisonPeriods}
/>
</div>
{/* Decline Table (Declining Lines) */}
<div className="h-80">
<GrowthTable
title={contextData ? "Declining Lines (Full Line Context)" : "Declining Lines - YoY (€)"}
data={displayData.bottomMovers}
type="decline"
periods={displayData.comparisonPeriods}
/>
</div>
</div>
{/* Row 3: Full-width Cumulative Sales YoY Chart */}
<div className="w-full">
<ExpandableCard title="Cumulative Sales YoY - Mensual" className="h-96">
<div className="flex flex-col h-full">
<div className="flex justify-end mb-2">
<div className="bg-slate-900 p-1 rounded-lg border border-slate-800 flex text-xs">
<button
onClick={(e) => { e.stopPropagation(); setCumulativeMetric('sellOut'); }}
className={`px-3 py-1 rounded-md transition-colors ${cumulativeMetric === 'sellOut' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Sell Out ()
</button>
<button
onClick={(e) => { e.stopPropagation(); setCumulativeMetric('units'); }}
className={`px-3 py-1 rounded-md transition-colors ${cumulativeMetric === 'units' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
>
Units
</button>
</div>
</div>
<div className="flex-1 min-h-0">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={cumulativeData}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="name" stroke="#64748b" />
<YAxis
stroke="#64748b"
tickFormatter={(val) => cumulativeMetric === 'sellOut' ? `${(val / 1000).toFixed(0)}k` : val.toLocaleString('de-DE')}
/>
<Tooltip content={(props: any) => <SeasonalityTooltip {...props} metric={cumulativeMetric} />} />
<Legend />
{data.availableYears.map((year, index) => (
<Line
key={year}
type="monotone"
dataKey={year}
name={year}
stroke={getYearColor(year)}
strokeWidth={3}
dot={{ r: 4, strokeWidth: 2 }}
activeDot={{ r: 6 }}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
</div>
</ExpandableCard>
</div>
{/* Keep secondary charts/details below main redesigned mockup */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Units Chart (Split by Year) */}
<ExpandableCard title={contextData ? "Total Product Line Units (Context)" : "Units Sold by Product Line (Overview)"} className="h-96">
<div className="flex flex-col h-full">
{contextData && <div className="text-xs text-indigo-400 font-semibold uppercase tracking-wider mb-2 text-right">Showing Full Product Line Data</div>}
<div className="flex-1">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={displayData.byLineOverviewSplit} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" vertical={false} />
<XAxis dataKey="name" stroke="#64748b" tick={{ fontSize: 10 }} interval={0} angle={-15} textAnchor="end" height={60} />
<YAxis
stroke="#64748b"
tickFormatter={(val) => {
if (val >= 1000000) return `${(val / 1000000).toFixed(1)}M`;
if (val >= 1000) return `${(val / 1000).toFixed(0)}k`;
return val;
}}
/>
<Tooltip content={(props: any) => <ComparisonTooltip {...props} metric="units" />} cursor={{ fill: '#1e293b' }} />
{displayData.availableYears.map((year, index) => (
<Bar
key={year}
dataKey={year}
name={year}
fill={getYearColor(year)}
radius={[4, 4, 0, 0]}
/>
))}
</BarChart>
</ResponsiveContainer>
</div>
</div>
</ExpandableCard>
{/* Country Chart */}
<ExpandableCard title="Revenue Distribution by Customer" className="h-96">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data.byCustomerSplit}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" vertical={false} />
<XAxis dataKey="name" stroke="#64748b" />
<YAxis stroke="#64748b" tickFormatter={(val) => `${(val / 1000).toFixed(0)}k`} />
<Tooltip content={(props: any) => <ComparisonTooltip {...props} metric="sellOut" />} cursor={{ fill: '#1e293b' }} />
{data.availableYears.map((year, index) => (
<Bar
key={year}
dataKey={year}
name={year}
fill={getYearColor(year)}
radius={[4, 4, 0, 0]}
/>
))}
</BarChart>
</ResponsiveContainer>
</ExpandableCard>
</div>
{/* Top Movers Table - Full Width */}
+8 -8
View File
@@ -51,30 +51,30 @@ const FilterBar: React.FC<FilterBarProps> = ({ filters, onFilterChange, options
</button>
{/* Filter dropdowns - always visible on desktop, toggle on mobile */}
<div className={`max-w-7xl mx-auto flex-wrap gap-2 md:gap-4 items-end ${isExpanded ? 'flex flex-col md:flex-row mt-3' : 'hidden md:flex md:flex-row'}`}>
<div className={`max-w-7xl mx-auto flex-wrap gap-2 md:gap-3 items-end ${isExpanded ? 'flex flex-col md:flex-row mt-3' : 'hidden md:flex md:flex-row'}`}>
<MultiSelectDropdown
label="Customer"
label="CUSTOMER"
selected={filters.customer}
options={options.customer}
onChange={(v) => onFilterChange('customer', v)}
className="w-full md:w-auto md:flex-1"
/>
<MultiSelectDropdown
label="Year"
label="YEAR"
selected={filters.year}
options={options.year}
onChange={(v) => onFilterChange('year', v)}
className="w-full md:w-auto md:flex-1"
/>
<MultiSelectDropdown
label="Month"
label="MONTH"
selected={filters.month}
options={options.month}
onChange={(v) => onFilterChange('month', v)}
className="w-full md:w-auto md:flex-1"
/>
<MultiSelectDropdown
label="Week"
label="WEEK"
selected={filters.week}
options={options.week}
onChange={(v) => onFilterChange('week', v)}
@@ -82,7 +82,7 @@ const FilterBar: React.FC<FilterBarProps> = ({ filters, onFilterChange, options
enableRangeSelect={true}
/>
<MultiSelectDropdown
label="Product Line"
label="PRODUCT LINE"
selected={filters.line}
options={options.line}
onChange={(v) => onFilterChange('line', v)}
@@ -96,7 +96,7 @@ const FilterBar: React.FC<FilterBarProps> = ({ filters, onFilterChange, options
className="w-full md:w-auto md:flex-1"
/>
<MultiSelectDropdown
label="Title"
label="TITLE"
selected={filters.title}
options={options.title}
onChange={(v) => onFilterChange('title', v)}
@@ -110,7 +110,7 @@ const FilterBar: React.FC<FilterBarProps> = ({ filters, onFilterChange, options
className="w-full md:w-auto md:flex-1"
/>
<MultiSelectDropdown
label="Stock"
label="STOCK"
selected={filters.stock}
options={options.stock}
onChange={(v) => onFilterChange('stock', v)}