Files
CrazeAnalytix/components/WeeklyGrid.tsx
T

88 lines
4.9 KiB
TypeScript
Raw Normal View History

2026-01-21 13:19:13 +01:00
import React, { useMemo } from 'react';
import { CombinedKPIs } from '../types';
import { pivotWeeklySalesData, WeeklyPivotRow } from '../services/dataProcessor';
interface WeeklyGridProps {
data: CombinedKPIs[];
}
const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
const { rows, weeks } = useMemo(() => pivotWeeklySalesData(data), [data]);
// Calculate totals per week
const weekTotals = useMemo(() => {
const totals: { [weekKey: string]: number } = {};
weeks.forEach(week => {
totals[week] = rows.reduce((sum, row) => sum + (row.unitsByWeek[week] || 0), 0);
});
return totals;
}, [rows, weeks]);
const renderGrowth = (current: number, previous: number) => {
if (!previous || previous === 0) return null;
const pct = ((current - previous) / previous) * 100;
const isPositive = pct >= 0;
return (
<div className={`text-[10px] flex items-center gap-0.5 mt-0.5 font-bold ${isPositive ? 'text-emerald-400' : 'text-red-400'}`}>
{isPositive ? '▲' : '▼'}{Math.abs(pct).toFixed(0)}%
</div>
);
};
return (
<div className="bg-slate-900/50 border border-white/10 rounded-xl overflow-hidden animate-fade-in shadow-2xl">
<div className="overflow-x-auto overflow-y-auto max-h-[calc(100vh-280px)]">
<table className="w-full text-left border-collapse min-w-[1000px]">
<thead className="sticky top-0 z-20">
<tr className="bg-slate-900 border-b border-white/10">
<th className="p-4 text-xs font-black text-slate-400 uppercase tracking-widest sticky left-0 z-30 bg-slate-900 border-r border-white/5 w-[300px]">Product Details</th>
{weeks.map(week => (
<th key={week} className="p-4 text-xs font-black text-slate-400 uppercase tracking-widest text-center border-r border-white/5">
{week.split('-')[1]}/{week.split('-')[0].slice(-2)}
</th>
))}
</tr>
<tr className="bg-slate-800/80 backdrop-blur-md border-b border-white/10 italic">
<th className="p-3 text-sm font-black text-white sticky left-0 z-30 bg-slate-800 border-r border-white/5">Totals</th>
{weeks.map((week, idx) => (
<th key={week} className="p-3 text-sm font-black text-white text-center border-r border-white/5">
{weekTotals[week]?.toLocaleString('de-DE')}
{renderGrowth(weekTotals[week], weekTotals[weeks[idx + 1]])}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-white/5">
{rows.map((row) => (
<tr key={row.id} className="hover:bg-white/[0.02] transition-colors group">
<td className="p-4 sticky left-0 z-10 bg-slate-900 md:bg-slate-900/90 backdrop-blur-sm group-hover:bg-slate-800 border-r border-white/5">
<div className="flex flex-col">
<span className="text-xs font-black text-indigo-400 uppercase tracking-wider mb-0.5">{row.sku}</span>
<span className="text-[11px] text-white/80 line-clamp-1 group-hover:line-clamp-none transition-all">{row.title}</span>
<span className="text-[10px] text-fuchsia-400/70 font-bold mt-1 uppercase tracking-tighter">{row.line}</span>
</div>
</td>
{weeks.map((week, idx) => {
const val = row.unitsByWeek[week] || 0;
const prevVal = row.unitsByWeek[weeks[idx + 1]] || 0;
return (
<td key={week} className="p-4 text-center border-r border-white/5 align-top">
<span className={`text-sm font-black ${val > 0 ? 'text-white' : 'text-slate-600'}`}>
{val > 0 ? val.toLocaleString('de-DE') : '-'}
</span>
{val > 0 && renderGrowth(val, prevVal)}
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default WeeklyGrid;