"use client"; import { forwardRef, useImperativeHandle, useRef } from "react"; import { Loader2, Plus, Table2, Upload } from "lucide-react"; import type { ColumnConfig, Document, TabularCell, } from "../shared/types"; import { TabularCell as TabularCellComponent } from "./TabularCell"; import { TREditColumnMenu } from "./TREditColumnMenu"; import { TABLE_CHECKBOX_CLASS, SkeletonDot, SkeletonLine, } from "../shared/TablePrimitive"; const SKELETON_COLS = 4; const SKELETON_ROWS = 5; const COL_W = "w-[300px] shrink-0"; const DOC_COL_W = "w-[332px] shrink-0"; // Pixel widths matching the CSS constants above const DOC_COL_W_PX = 332; const DATA_COL_W_PX = 300; const STICKY_LEFT_PX = DOC_COL_W_PX; export interface TRTableHandle { scrollToCell: (colIdx: number, rowIdx: number) => void; } interface Props { loading: boolean; columns: ColumnConfig[]; documents: Document[]; cells: TabularCell[]; savingColumn: boolean; savingColumnsConfig: boolean; selectedDocIds: string[]; uploadingFilenames?: string[]; dragOverFiles?: boolean; 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, uploadingFilenames = [], dragOverFiles = false, highlightedCell, onSelectionChange, onExpand, onCitationClick, onUpdateColumn, onDeleteColumn, onAddColumn, onAddDocuments, }, ref, ) { const stickyCellBg = "bg-[#fafbfc]"; const scrollContainerRef = useRef(null); const sortedColumns = [...columns].sort((a, b) => a.index - b.index); const totalContentWidth = DOC_COL_W_PX + sortedColumns.length * DATA_COL_W_PX + 32; const skeletonContentWidth = DOC_COL_W_PX + SKELETON_COLS * 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 && uploadingFilenames.length === 0 ) { return (
Document
{dragOverFiles && (
)}

Tabular Review

Add columns and documents to get started.

); } return (
{/* Header */}
{ if (el) el.indeterminate = someSelected; }} onChange={toggleAll} className={TABLE_CHECKBOX_CLASS} /> Document
{columns.map((col) => (
{col.name}
))}
{/* Rows */}
{dragOverFiles && (
)} {uploadingFilenames.map((filename) => (
{filename}
{sortedColumns.map((col) => (
))}
))} {documents.map((doc, docIdx) => { const baseRowBg = docIdx % 2 === 0 ? stickyCellBg : "bg-gray-50"; const rowBg = selectedDocIds.includes(doc.id) ? "bg-gray-100" : baseRowBg; return (
toggleDoc(doc.id)} className={TABLE_CHECKBOX_CLASS} /> {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, ) } /> )}
); })}
); })}
); });