mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 11:55:22 +02:00
feat: redesign dashboard layout and colors to match mockup
This commit is contained in:
+56
-92
@@ -22,6 +22,16 @@ interface DashboardProps {
|
||||
|
||||
const COLORS = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6', '#0ea5e9'];
|
||||
|
||||
const getYearColor = (year: string | number) => {
|
||||
const yStr = year.toString();
|
||||
if (yStr === '2024') return '#6366f1'; // Indigo
|
||||
if (yStr === '2025') return '#ec4899'; // Pink
|
||||
if (yStr === '2026') return '#10b981'; // Emerald
|
||||
const yInt = parseInt(yStr);
|
||||
if (isNaN(yInt)) return '#6366f1';
|
||||
return COLORS[yInt % COLORS.length];
|
||||
};
|
||||
|
||||
// Reusable Expandable Card Component
|
||||
const ExpandableCard: React.FC<{ title: string; children: React.ReactNode; className?: string }> = ({ title, children, className }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
@@ -98,11 +108,9 @@ const MultiYearKPICard: React.FC<{
|
||||
const sortedYears = [...availableYears].sort((a, b) => parseInt(b) - parseInt(a));
|
||||
|
||||
const formatValue = (val: number) => {
|
||||
if (metric === 'sellOut') {
|
||||
if (metric === 'sellOut') {
|
||||
return `€${val.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
|
||||
}
|
||||
}
|
||||
return val.toLocaleString('de-DE');
|
||||
};
|
||||
|
||||
@@ -110,10 +118,10 @@ const MultiYearKPICard: React.FC<{
|
||||
const displayTitle = contextData ? `${title} (Selected Item)` : title;
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-br from-surface to-slate-900 border border-border rounded-xl p-3 md:p-6 flex flex-col justify-center">
|
||||
<h3 className="text-xs md:text-sm font-medium text-slate-400 mb-2 md:mb-3">{displayTitle}</h3>
|
||||
<div className="bg-slate-900/40 border border-slate-800 rounded-xl p-5 md:p-6 flex flex-col justify-center shadow-lg">
|
||||
<h3 className="text-xs md:text-sm font-semibold text-slate-400 uppercase tracking-wider mb-4">{displayTitle}</h3>
|
||||
|
||||
{sortedYears.length === 0 && <p className="text-3xl font-bold text-white">0</p>}
|
||||
{sortedYears.length === 0 && <p className="text-2xl font-bold text-white">0</p>}
|
||||
|
||||
{sortedYears.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
@@ -134,7 +142,7 @@ const MultiYearKPICard: React.FC<{
|
||||
const pct = ((currentValue - prevValue) / prevValue) * 100;
|
||||
growthElement = (
|
||||
<span className={`text-xs ml-2 font-bold ${pct >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{pct > 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
{pct >= 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -146,7 +154,7 @@ const MultiYearKPICard: React.FC<{
|
||||
const pct = ((contextValue - prevContextValue) / prevContextValue) * 100;
|
||||
contextGrowthElement = (
|
||||
<span className={`text-[10px] ml-2 font-bold ${pct >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{pct > 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
{pct >= 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -154,22 +162,18 @@ const MultiYearKPICard: React.FC<{
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={year} className="flex flex-col border-b border-slate-800 pb-2 last:border-0">
|
||||
<div className="flex justify-between items-end">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs text-slate-500 font-mono mb-0.5">{year}</span>
|
||||
<div className="flex items-center">
|
||||
<span className="text-xl font-bold text-slate-200 leading-none">
|
||||
<div key={year} className="flex flex-col border-b border-slate-800/80 pb-3 last:border-0 last:pb-0">
|
||||
<span className="text-xs text-slate-500 font-medium mb-1">{year}</span>
|
||||
<div className="flex items-baseline">
|
||||
<span className="text-2xl font-bold text-white tracking-tight leading-none">
|
||||
{formatValue(currentValue)}
|
||||
</span>
|
||||
{growthElement}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Context Row (Product Line Total) */}
|
||||
{contextData && contextValue > 0 && (
|
||||
<div className="mt-1 flex flex-wrap items-center justify-between bg-slate-800/50 p-2 rounded text-xs">
|
||||
<div className="mt-2 flex flex-wrap items-center justify-between bg-slate-850/50 p-2 rounded text-xs border border-slate-800">
|
||||
<span className="text-slate-400 mr-2">Total Product Line:</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-slate-300 font-medium">{formatValue(contextValue)}</span>
|
||||
@@ -232,7 +236,7 @@ const SeasonalityTooltip = ({ active, payload, label, metric }: any) => {
|
||||
const pct = ((currVal - prevVal) / prevVal) * 100;
|
||||
growthEl = (
|
||||
<span className={`text-xs ml-2 font-bold ${pct >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{pct > 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
{pct >= 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -282,7 +286,7 @@ const ComparisonTooltip = ({ active, payload, label, metric }: any) => {
|
||||
const pct = ((currVal - prevVal) / prevVal) * 100;
|
||||
growthEl = (
|
||||
<span className={`text-xs ml-2 font-bold ${pct >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{pct > 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
{pct >= 0 ? '▲' : '▼'} {Math.abs(pct).toFixed(1)}%
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -361,62 +365,35 @@ const GrowthTable: React.FC<{
|
||||
<thead className="bg-slate-950 text-xs uppercase font-semibold text-slate-500 sticky top-0 z-10 shadow-sm">
|
||||
<tr>
|
||||
<th
|
||||
className="px-4 py-3 bg-slate-950 min-w-[150px] cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
className="px-4 py-3 bg-slate-950 min-w-[120px] cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
onClick={() => requestSort('line')}
|
||||
>
|
||||
<div className="flex items-center">Product Line {getSortIndicator('line')}</div>
|
||||
<div className="flex items-center font-bold">Product Line {getSortIndicator('line')}</div>
|
||||
</th>
|
||||
|
||||
{/* Sell Out Columns */}
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 text-slate-400 cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
onClick={() => requestSort('previousYearSellOut')}
|
||||
>
|
||||
<div className="flex items-center justify-end">Sell Out {periods.previous} {getSortIndicator('previousYearSellOut')}</div>
|
||||
<div className="flex items-center justify-end font-bold">Sell Out {periods.previous} {getSortIndicator('previousYearSellOut')}</div>
|
||||
</th>
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 text-slate-200 cursor-pointer hover:text-white group select-none transition-colors"
|
||||
onClick={() => requestSort('currentYearSellOut')}
|
||||
>
|
||||
<div className="flex items-center justify-end">Sell Out {periods.current} {getSortIndicator('currentYearSellOut')}</div>
|
||||
<div className="flex items-center justify-end font-bold">Sell Out {periods.current} {getSortIndicator('currentYearSellOut')}</div>
|
||||
</th>
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
onClick={() => requestSort('sellOutGrowthValue')}
|
||||
>
|
||||
<div className="flex items-center justify-end">SO Diff {getSortIndicator('sellOutGrowthValue')}</div>
|
||||
<div className="flex items-center justify-end font-bold">SO Diff {getSortIndicator('sellOutGrowthValue')}</div>
|
||||
</th>
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
onClick={() => requestSort('sellOutGrowthPercentage')}
|
||||
>
|
||||
<div className="flex items-center justify-end">SO Growth % {getSortIndicator('sellOutGrowthPercentage')}</div>
|
||||
</th>
|
||||
|
||||
{/* Units Columns */}
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 text-slate-400 cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
onClick={() => requestSort('previousYearUnits')}
|
||||
>
|
||||
<div className="flex items-center justify-end">Units {periods.previous} {getSortIndicator('previousYearUnits')}</div>
|
||||
</th>
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 text-slate-200 cursor-pointer hover:text-white group select-none transition-colors"
|
||||
onClick={() => requestSort('currentYearUnits')}
|
||||
>
|
||||
<div className="flex items-center justify-end">Units {periods.current} {getSortIndicator('currentYearUnits')}</div>
|
||||
</th>
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
onClick={() => requestSort('unitsGrowthValue')}
|
||||
>
|
||||
<div className="flex items-center justify-end">Units Diff {getSortIndicator('unitsGrowthValue')}</div>
|
||||
</th>
|
||||
<th
|
||||
className="px-4 py-3 text-right bg-slate-950 cursor-pointer hover:text-slate-300 group select-none transition-colors"
|
||||
onClick={() => requestSort('unitsGrowthPercentage')}
|
||||
>
|
||||
<div className="flex items-center justify-end">Units Growth % {getSortIndicator('unitsGrowthPercentage')}</div>
|
||||
<div className="flex items-center justify-end font-bold">SO Growth % {getSortIndicator('sellOutGrowthPercentage')}</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -426,34 +403,21 @@ const GrowthTable: React.FC<{
|
||||
<tr key={idx} className="hover:bg-slate-800/50">
|
||||
<td className="px-4 py-2 font-medium">{item.line}</td>
|
||||
|
||||
{/* Sell Out Columns */}
|
||||
<td className="px-4 py-2 text-right text-slate-400">€{item.previousYearSellOut.toLocaleString('de-DE', { maximumFractionDigits: 0 })}</td>
|
||||
<td className="px-4 py-2 text-right font-medium text-slate-200">€{item.currentYearSellOut.toLocaleString('de-DE', { maximumFractionDigits: 0 })}</td>
|
||||
<td className={`px-4 py-2 text-right font-medium ${item.sellOutGrowthValue >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
|
||||
{item.sellOutGrowthValue > 0 ? '+' : ''}€{item.sellOutGrowthValue.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-right">
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${item.sellOutGrowthPercentage >= 0 ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400'}`}>
|
||||
{item.sellOutGrowthPercentage.toFixed(0)}%
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* Units Columns */}
|
||||
<td className="px-4 py-2 text-right text-slate-400">{item.previousYearUnits.toLocaleString('de-DE')}</td>
|
||||
<td className="px-4 py-2 text-right font-medium text-slate-200">{item.currentYearUnits.toLocaleString('de-DE')}</td>
|
||||
<td className={`px-4 py-2 text-right font-medium ${item.unitsGrowthValue >= 0 ? 'text-violet-400' : 'text-orange-400'}`}>
|
||||
{item.unitsGrowthValue > 0 ? '+' : ''}{item.unitsGrowthValue.toLocaleString('de-DE')}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-right">
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${item.unitsGrowthPercentage >= 0 ? 'bg-violet-500/10 text-violet-400' : 'bg-orange-500/10 text-orange-400'}`}>
|
||||
{item.unitsGrowthPercentage.toFixed(0)}%
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-bold ${item.sellOutGrowthPercentage >= 0 ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400'}`}>
|
||||
{item.sellOutGrowthPercentage >= 0 ? '+' : ''}{item.sellOutGrowthPercentage.toFixed(0)}%
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={9} className="px-4 py-6 text-center text-slate-500 italic">
|
||||
<td colSpan={5} className="px-4 py-6 text-center text-slate-500 italic">
|
||||
Insufficient data to calculate {type}. (Select at least 2 distinct years/periods)
|
||||
</td>
|
||||
</tr>
|
||||
@@ -463,7 +427,7 @@ const GrowthTable: React.FC<{
|
||||
</div>
|
||||
</ExpandableCard>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Dashboard: React.FC<DashboardProps> = ({
|
||||
data,
|
||||
@@ -550,16 +514,16 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
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">
|
||||
|
||||
{/* Ads Performance Section - Condensed Layout */}
|
||||
{/* Ads Performance Section - Overhauled Layout to match Mockup */}
|
||||
{adsKPIsByYear && availableAdsYears.length > 0 && (
|
||||
<div className="bg-gradient-to-br from-fuchsia-950/20 to-indigo-950/30 border border-fuchsia-500/30 rounded-2xl p-4 animate-fade-in shadow-xl">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<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>
|
||||
</div>
|
||||
<h3 className="text-xs font-black text-fuchsia-400 uppercase tracking-widest">Advertising Performance</h3>
|
||||
<div className="ml-auto px-2 py-0.5 bg-slate-900/50 rounded-full border border-fuchsia-500/10 text-[9px] text-fuchsia-300/70 font-bold uppercase">
|
||||
<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>
|
||||
@@ -579,25 +543,25 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
: (isPositive ? 'text-emerald-400' : 'text-red-400');
|
||||
|
||||
return (
|
||||
<span className={`text-[9px] font-black ml-1 ${colorClass} whitespace-nowrap`}>
|
||||
<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/40 rounded-xl p-3 border border-white/5">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-[10px] font-black text-slate-400 font-mono bg-slate-900 px-1.5 py-0.5 rounded">{year}</span>
|
||||
<div className="h-[1px] flex-1 bg-gradient-to-r from-slate-800 to-transparent"></div>
|
||||
<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>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
{/* Ad Spend */}
|
||||
<div className="group">
|
||||
<span className="text-[9px] font-bold text-slate-500 uppercase tracking-tighter block mb-0.5">Spend</span>
|
||||
<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-sm font-black text-fuchsia-400 truncate">
|
||||
<span className="text-base font-bold text-fuchsia-400 truncate">
|
||||
€{kpi.totalSpend.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
|
||||
</span>
|
||||
{renderGrowth(kpi.totalSpend, prevKpi?.totalSpend)}
|
||||
@@ -605,9 +569,9 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
</div>
|
||||
{/* Attributed Sales */}
|
||||
<div className="group">
|
||||
<span className="text-[9px] font-bold text-slate-500 uppercase tracking-tighter block mb-0.5">Sales</span>
|
||||
<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-sm font-black text-emerald-400 truncate">
|
||||
<span className="text-base font-bold text-emerald-400 truncate">
|
||||
€{kpi.attributedSales.toLocaleString('de-DE', { maximumFractionDigits: 0 })}
|
||||
</span>
|
||||
{renderGrowth(kpi.attributedSales, prevKpi?.attributedSales)}
|
||||
@@ -615,9 +579,9 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
</div>
|
||||
{/* ACOS */}
|
||||
<div className="group">
|
||||
<span className="text-[9px] font-bold text-slate-500 uppercase tracking-tighter block mb-0.5">ACOS</span>
|
||||
<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-sm font-black truncate ${kpi.acos <= 30 ? 'text-emerald-400' : kpi.acos <= 50 ? 'text-amber-400' : 'text-red-400'}`}>
|
||||
<span className="text-base font-bold text-emerald-400 truncate">
|
||||
{kpi.acos.toFixed(1)}%
|
||||
</span>
|
||||
{renderGrowth(kpi.acos, prevKpi?.acos, true)}
|
||||
@@ -625,9 +589,9 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
</div>
|
||||
{/* ROAS */}
|
||||
<div className="group">
|
||||
<span className="text-[9px] font-bold text-slate-500 uppercase tracking-tighter block mb-0.5">ROAS</span>
|
||||
<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-sm font-black truncate ${kpi.roas >= 3 ? 'text-emerald-400' : kpi.roas >= 2 ? 'text-amber-400' : 'text-red-400'}`}>
|
||||
<span className="text-base font-bold text-emerald-400 truncate">
|
||||
{kpi.roas.toFixed(2)}x
|
||||
</span>
|
||||
{renderGrowth(kpi.roas, prevKpi?.roas)}
|
||||
@@ -659,7 +623,7 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Grid */}
|
||||
{/* Main Grid matching Mockup Columns */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
{/* Left Column */}
|
||||
@@ -704,7 +668,7 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
key={year}
|
||||
dataKey={`${year}_${top10Metric === 'sellOut' ? 'value' : 'units'}`}
|
||||
name={year}
|
||||
fill={COLORS[index % COLORS.length]}
|
||||
fill={getYearColor(year)}
|
||||
radius={[0, 4, 4, 0]}
|
||||
/>
|
||||
))}
|
||||
@@ -762,7 +726,7 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
key={year}
|
||||
dataKey={year}
|
||||
name={year}
|
||||
fill={COLORS[index % COLORS.length]}
|
||||
fill={getYearColor(year)}
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
))}
|
||||
@@ -808,7 +772,7 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
type="monotone"
|
||||
dataKey={year}
|
||||
name={year}
|
||||
stroke={COLORS[index % COLORS.length]}
|
||||
stroke={getYearColor(year)}
|
||||
strokeWidth={3}
|
||||
dot={{ r: 4, strokeWidth: 2 }}
|
||||
activeDot={{ r: 6 }}
|
||||
@@ -856,7 +820,7 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
type="monotone"
|
||||
dataKey={year}
|
||||
name={year}
|
||||
stroke={COLORS[index % COLORS.length]}
|
||||
stroke={getYearColor(year)}
|
||||
strokeWidth={3}
|
||||
dot={{ r: 4, strokeWidth: 2 }}
|
||||
activeDot={{ r: 6 }}
|
||||
@@ -882,7 +846,7 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
key={year}
|
||||
dataKey={year}
|
||||
name={year}
|
||||
fill={COLORS[index % COLORS.length]}
|
||||
fill={getYearColor(year)}
|
||||
radius={[4, 4, 0, 0]}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user