'use client'; import { useState, useMemo, Fragment } from 'react'; import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getExpandedRowModel, ColumnDef, flexRender, SortingState, Row, } from '@tanstack/react-table'; import { ArrowUpDown, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ChevronDown, ChevronRight as ChevronRightIcon, FileText, } from 'lucide-react'; import type { SpanItem, PaginatedSpans } from '../types'; import { VALENCE_LABELS, VALENCE_COLORS, INTENSITY_LABELS, DOMAIN_LABELS } from '../types'; import { ReviewModal } from './ReviewModal'; interface SpansTableProps { spans: PaginatedSpans; onPageChange?: (page: number) => void; } /** * Spans table with expandable rows and drill-down to full review. */ export function SpansTable({ spans, onPageChange }: SpansTableProps) { const [sorting, setSorting] = useState([]); const [expanded, setExpanded] = useState>({}); const [selectedReview, setSelectedReview] = useState<{ reviewId: string; spanId: string; } | null>(null); const columns = useMemo[]>( () => [ { id: 'expander', header: '', cell: ({ row }) => ( ), }, { accessorKey: 'span_text', header: 'Text', cell: ({ row }) => (

{row.original.span_text}

), }, { accessorKey: 'urt_primary', header: ({ column }) => ( ), cell: ({ row }) => ( {row.original.urt_primary || '-'} ), }, { accessorKey: 'valence', header: 'Sentiment', cell: ({ row }) => { const valence = row.original.valence; if (!valence) return -; return ( {VALENCE_LABELS[valence] || valence} ); }, }, { accessorKey: 'intensity', header: 'Intensity', cell: ({ row }) => ( {row.original.intensity ? INTENSITY_LABELS[row.original.intensity] || row.original.intensity : '-'} ), }, { accessorKey: 'review_time', header: ({ column }) => ( ), cell: ({ row }) => ( {row.original.review_time ? new Date(row.original.review_time).toLocaleDateString() : '-'} ), }, ], [] ); const table = useReactTable({ data: spans.items, columns, state: { sorting, expanded, }, onSortingChange: setSorting, onExpandedChange: setExpanded as any, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), getExpandedRowModel: getExpandedRowModel(), getRowCanExpand: () => true, initialState: { pagination: { pageSize: 10 }, }, }); const totalPages = Math.ceil(spans.total / spans.page_size); // Render expanded row content const renderExpandedRow = (row: Row) => { const span = row.original; return (
Full Text

{span.span_text}

Span ID

{span.span_id}

Review ID

{span.source_review_id || '-'}

Entity

{span.entity || '-'}

Domain

{span.urt_primary ? DOMAIN_LABELS[span.urt_primary[0]] || span.urt_primary[0] : '-'}

{/* View Full Review Button */} {span.source_review_id && (
)}
); }; return (

Classified Spans

{spans.total} total spans - Click row to expand

{spans.items.length === 0 ? (
No spans found
) : ( <>
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel().rows.map((row) => ( row.toggleExpanded()} > {row.getVisibleCells().map((cell) => ( ))} {row.getIsExpanded() && renderExpandedRow(row)} ))}
{header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )}
{flexRender( cell.column.columnDef.cell, cell.getContext() )}
{/* Pagination */}
Page {spans.page} of {totalPages} ({spans.total} spans)
)} {/* Review Modal for drill-down */} setSelectedReview(null)} />
); }