use client
import React, { useState, useMemo, useCallback } from 'react';
interface MatrixRow {
label: string;
values: number[];
}
interface MatrixData {
columns: string[];
rows: MatrixRow[];
}
interface TooltipData {
x: number;
y: number;
value: number;
rowLabel: string;
colLabel: string;
}
const demoData: MatrixData = {
columns: ['Q1 2024', 'Q2 2024', 'Q3 2024', 'Q4 2024', 'Total'],
rows: [
{ label: 'North Region', values: [85, 92, 78, 95, 350] },
{ label: 'South Region', values: [72, 88, 94, 80, 334] },
{ label: 'East Region', values: [91, 76, 89, 87, 343] },
{ label: 'West Region', values: [68, 95, 82, 91, 336] },
{ label: 'Central Region', values: [79, 84, 90, 77, 330] },
{ label: 'Total', values: [395, 435, 433, 430, 1693] },
],
};
const MatrixTable: React.FC = () => {
const [tooltip, setTooltip] = useState<TooltipData | null>(null);
const [sortColumn, setSortColumn] = useState<number | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const getMaxValue = useMemo(() => {
let max = 0;
demoData.rows.forEach(row => {
row.values.forEach(val => {
if (val > max) max = val;
});
});
return max;
}, []);
const getHeatmapColor = useCallback((value: number): string => {
const intensity = value / getMaxValue;
if (intensity === 0) return '#0A0A0A';
const blueValue = Math.round(255 * intensity);
const greenValue = Math.round(180 * intensity);
return `rgba(${Math.round(20 * intensity)}, ${greenValue}, ${blueValue}, ${0.3 + intensity * 0.7})`;
}, [getMaxValue]);
const sortedRows = useMemo(() => {
if (sortColumn === null) return demoData.rows;
return [...demoData.rows].sort((a, b) => {
const comparison = a.values[sortColumn] - b.values[sortColumn];
return sortDirection === 'asc' ? comparison : -comparison;
});
}, [sortColumn, sortDirection]);
const handleColumnHeaderClick = useCallback((colIndex: number) => {
if (colIndex === demoData.columns.length - 1) return;
setSortColumn(prev => {
if (prev === colIndex) {
setSortDirection(dir => dir === 'asc' ? 'desc' : 'asc');
return colIndex;
}
setSortDirection('desc');
return colIndex;
});
}, []);
const handleCellHover = useCallback((e: React.MouseEvent, value: number, rowLabel: string, colLabel: string) => {
const rect = (e.target as HTMLElement).getBoundingClientRect();
setTooltip({
x: rect.left + rect.width / 2,
y: rect.top - 10,
value,
rowLabel,
colLabel,
});
}, []);
const handleCellLeave = useCallback(() => {
setTooltip(null);
}, []);
return (
<div className="relative bg-[#0A0A0A] p-6 rounded-xl border border-[#C9A84C]/20">
<h2 className="text-2xl font-bold text-[#C9A84C] mb-6">Regional Performance Matrix</h2>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr>
<th className="text-left p-3 text-[#F5F5F0]/60 font-medium border-b border-[#C9A84C]/20">
Region
</th>
{demoData.columns.map((col, idx) => (
<th
key={idx}
className={`p-3 text-[#F5F5F0]/60 font-medium border-b border-[#C9A84C]/20 cursor-pointer hover:text-[#C9A84C] transition-colors ${
idx === demoData.columns.length - 1 ? 'bg-[#C9A84C]/10' : ''
}`}
onClick={() => handleColumnHeaderClick(idx)}
>
<div className="flex items-center justify-center gap-1">
{col}
{sortColumn === idx && (
<span className="text-[#C9A84C] text-xs">
{sortDirection === 'asc' ? '▲' : '▼'}
</span>
)}
</div>
</th>
))}
</tr>
</thead>
<tbody>
{sortedRows.map((row, rowIdx) => (
<tr
key={rowIdx}
className={`hover:bg-white/5 transition-colors ${
row.label === 'Total' ? 'bg-[#C9A84C]/10 border-t-2 border-[#C9A84C]/30' : ''
}`}
>
<td className="p-3 text-[#F5F5F0]/80 font-medium border-b border-[#C9A84C]/20">
{row.label}
</td>
{row.values.map((value, colIdx) => (
<td
key={colIdx}
className={`p-3 text-center border-b border-[#C9A84C]/20 transition-all duration-200 ${
colIdx === demoData.columns.length - 1 ? 'font-bold text-[#C9A84C]' : ''
}`}
style={{
backgroundColor: getHeatmapColor(value),
color: value > getMaxValue * 0.6 ? '#F5F5F0' : '#F5F5F0/70',
}}
onMouseEnter={(e) => handleCellHover(e, value, row.label, demoData.columns[colIdx])}
onMouseLeave={handleCellLeave}
>
{value}
{colIdx === demoData.columns.length - 1 && row.label !== 'Total' && (
<span className="ml-1 text-xs text-[#C9A84C]/60">∑</span>
)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{tooltip && (
<div
className="fixed z-50 px-4 py-2 bg-[#1A1A1A] border border-[#C9A84C]/30 rounded-lg shadow-2xl pointer-events-none"
style={{
left: tooltip.x,
top: tooltip.y,
transform: 'translate(-50%, -100%)',
}}
>
<div className="text-[#C9A84C] text-sm font-semibold mb-1">
{tooltip.rowLabel} — {tooltip.colLabel}
</div>
<div className="text-[#F5F5F0] text-lg font-bold">
Value: {tooltip.value}
</div>
<div className="text-[#F5F5F0]/50 text-xs mt-1">
{((tooltip.value / getMaxValue) * 100).toFixed(1)}% of max
</div>
</div>
)}
<div className="mt-4 flex items-center gap-2 text-[#F5F5F0]/40 text-xs">
<span>Heatmap intensity based on value proportion</span>
<span className="text-[#C9A84C]">●</span>
<span>Click column headers to sort</span>
<span className="text-[#C9A84C]">●</span>
<span>Hover cells for details</span>
</div>
</div>
);
};
export default MatrixTable;