"use client"; import { forwardRef, useImperativeHandle, useRef } from "react"; import { Plus, Table2 } from "lucide-react"; import type { ColumnConfig, MikeDocument, TabularCell } from "../shared/types"; import { TabularCell as TabularCellComponent } from "./TabularCell"; import { TREditColumnMenu } from "./TREditColumnMenu"; const SKELETON_COLS = 4; const SKELETON_ROWS = 5; const COL_W = "w-[300px] shrink-0"; const CHECK_W = "w-8 shrink-0"; // Pixel widths matching the CSS constants above const CHECK_W_PX = 32; // w-8 = 2rem = 32px const DOC_COL_W_PX = 300; const DATA_COL_W_PX = 300; const STICKY_LEFT_PX = CHECK_W_PX + DOC_COL_W_PX; // 332px export interface TRTableHandle { scrollToCell: (colIdx: number, rowIdx: number) => void; } interface Props { loading: boolean; columns: ColumnConfig[]; documents: MikeDocument[]; cells: TabularCell[]; savingColumn: boolean; savingColumnsConfig: boolean; selectedDocIds: string[]; highlightedCell?: { colIdx: number; rowIdx: number } | null; onSelectionChange: (ids: string[]) => void; onExpand: (cell: TabularCell) => void; onCitationClick: (cell: TabularCell, page: number, quote: string) => void; onUpdateColumn: (col: ColumnConfig) => void; onDeleteColumn: (colIndex: number) => void; onAddColumn: () => void; onAddDocuments: () => void; } export const TRTable = forwardRef(function TRTable( { loading, columns, documents, cells, savingColumn, savingColumnsConfig, selectedDocIds, highlightedCell, onSelectionChange, onExpand, onCitationClick, onUpdateColumn, onDeleteColumn, onAddColumn, onAddDocuments, }, ref, ) { const scrollContainerRef = useRef(null); const sortedColumns = [...columns].sort((a, b) => a.index - b.index); const totalContentWidth = CHECK_W_PX + DOC_COL_W_PX + sortedColumns.length * DATA_COL_W_PX + 32; useImperativeHandle(ref, () => ({ scrollToCell(colIdx: number, rowIdx: number) { const container = scrollContainerRef.current; if (!container) return; // Vertical: find actual row via DOM (handles variable row heights) const allRows = container.querySelectorAll( ":scope > div.flex.min-w-full", ); const targetRow = allRows[rowIdx]; if (targetRow) { container.scrollTo({ top: Math.max(0, targetRow.offsetTop - 40), behavior: "smooth", }); } // Horizontal: fixed column widths — center the target column in view const targetScrollLeft = STICKY_LEFT_PX + colIdx * DATA_COL_W_PX - container.clientWidth / 2 + DATA_COL_W_PX / 2; container.scrollLeft = Math.max(0, targetScrollLeft); }, })); function getCell(docId: string, colIdx: number) { return cells.find( (c) => c.document_id === docId && c.column_index === colIdx, ); } const allSelected = documents.length > 0 && documents.every((d) => selectedDocIds.includes(d.id)); const someSelected = !allSelected && documents.some((d) => selectedDocIds.includes(d.id)); function toggleAll() { if (allSelected) { onSelectionChange([]); } else { onSelectionChange(documents.map((d) => d.id)); } } function toggleDoc(id: string) { if (selectedDocIds.includes(id)) { onSelectionChange(selectedDocIds.filter((x) => x !== id)); } else { onSelectionChange([...selectedDocIds, id]); } } if (loading) { return (
{/* Header */}
Document
{Array.from({ length: SKELETON_COLS }).map((_, i) => (
))}
{/* Rows */} {Array.from({ length: SKELETON_ROWS }).map((_, row) => (
{Array.from({ length: SKELETON_COLS }).map((_, col) => (
))}
))}
); } if (columns.length === 0 && documents.length === 0) { return (
Document

Tabular Review

Add columns and documents to get started.

); } return (
{/* Header */}
{ if (el) el.indeterminate = someSelected; }} onChange={toggleAll} className="h-2.5 w-2.5 rounded border-gray-200 cursor-pointer accent-black" />
Document
{columns.map((col) => (
{col.name}
))}
{/* Rows */} {documents.map((doc, docIdx) => { const rowBg = selectedDocIds.includes(doc.id) ? "bg-gray-100" : docIdx % 2 === 0 ? "bg-white" : "bg-gray-50"; return (
toggleDoc(doc.id)} className="h-2.5 w-2.5 shrink-0 rounded border-gray-200 cursor-pointer accent-black" />
{doc.filename}
{columns.map((col) => { const cell = getCell(doc.id, col.index); const colPos = sortedColumns.findIndex( (c) => c.index === col.index, ); const isHighlighted = highlightedCell?.colIdx === colPos && highlightedCell?.rowIdx === docIdx; return (
{cell && ( onExpand(cell)} onCitationClick={(page, quote) => onCitationClick( cell, page, quote, ) } /> )}
); })}
); })}
); });