Library/Components/Tables/MatrixTable

MatrixTable

react
DiffViewerTablesInvoiceTable
MatrixTable.tsx — preview
reactlive
MatrixTable.tsx
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;
Same category

Similar components

DataTable
DataTable
AnalyticsTable
AnalyticsTable
TablePivot
TablePivot
DataTable
DataTable