'use client'; import { Fragment, memo, useMemo, useRef } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { ChevronDown, ChevronRight } from 'lucide-react'; import type { FinancialStatementPeriod, SurfaceFinancialRow, DetailFinancialRow } from '@/lib/types'; import { cn } from '@/lib/utils'; import type { StatementInspectorSelection, StatementTreeNode, StatementTreeSection } from '@/lib/financials/statement-view-model'; type MatrixRow = SurfaceFinancialRow | DetailFinancialRow; type StatementMatrixProps = { periods: FinancialStatementPeriod[]; sections: StatementTreeSection[]; selectedRowRef: StatementInspectorSelection | null; onToggleRow: (key: string) => void; onSelectRow: (selection: StatementInspectorSelection) => void; renderCellValue: (row: MatrixRow, periodId: string, previousPeriodId: string | null) => string; periodLabelFormatter: (value: string) => string; dense?: boolean; virtualized?: boolean; }; function isSurfaceNode(node: StatementTreeNode): node is Extract { return node.kind === 'surface'; } function rowSelected( node: StatementTreeNode, selectedRowRef: StatementInspectorSelection | null ) { if (!selectedRowRef) { return false; } if (node.kind === 'surface') { return selectedRowRef.kind === 'surface' && selectedRowRef.key === node.row.key; } return selectedRowRef.kind === 'detail' && selectedRowRef.key === node.row.key && selectedRowRef.parentKey === node.parentSurfaceKey; } function surfaceBadges(node: Extract) { const badges: Array<{ label: string; tone: 'default' | 'warning' | 'muted' }> = []; if (node.row.resolutionMethod === 'formula_derived') { badges.push({ label: 'Formula', tone: node.row.confidence === 'low' ? 'warning' : 'default' }); } if (node.row.resolutionMethod === 'not_meaningful') { badges.push({ label: 'N/M', tone: 'muted' }); } if (node.row.confidence === 'low') { badges.push({ label: 'Low confidence', tone: 'warning' }); } const detailCount = node.row.detailCount ?? node.directDetailCount; if (detailCount > 0) { badges.push({ label: `${detailCount} details`, tone: 'default' }); } return badges; } function badgeClass(tone: 'default' | 'warning' | 'muted', dense?: boolean) { const baseClasses = dense ? 'rounded border px-1 py-0.5 text-[9px] uppercase tracking-[0.12em]' : 'rounded-full border px-2 py-0.5 text-[10px] uppercase tracking-[0.14em]'; if (tone === 'warning') { return cn(baseClasses, 'border-[#84614f] bg-[rgba(112,76,54,0.22)] text-[#ffd7bf]'); } if (tone === 'muted') { return cn(baseClasses, 'border-[color:var(--line-weak)] bg-[rgba(80,85,92,0.16)] text-[color:var(--terminal-muted)]'); } return cn(baseClasses, 'border-[color:var(--line-weak)] bg-[rgba(88,102,122,0.16)] text-[color:var(--terminal-bright)]'); } // Flatten tree nodes for virtualization type FlattenedNode = { node: StatementTreeNode; sectionKey: string; sectionLabel?: string; isSectionHeader?: boolean; }; function flattenSections(sections: StatementTreeSection[]): FlattenedNode[] { const result: FlattenedNode[] = []; function flattenNodes(nodes: StatementTreeNode[], sectionKey: string): void { for (const node of nodes) { result.push({ node, sectionKey }); if (node.kind === 'surface' && node.expanded && node.children.length > 0) { flattenNodes(node.children, sectionKey); } } } for (const section of sections) { if (section.label) { result.push({ node: {} as StatementTreeNode, sectionKey: section.key, sectionLabel: section.label, isSectionHeader: true }); } flattenNodes(section.nodes, section.key); } return result; } function renderNodes(props: StatementMatrixProps & { nodes: StatementTreeNode[]; dense?: boolean }) { const { dense = false } = props; const buttonSize = dense ? 'size-7' : 'size-11'; const buttonClass = dense ? 'rounded' : 'rounded-lg'; const labelSize = dense ? 'text-[13px]' : 'text-sm'; const detailLabelSize = dense ? 'text-xs' : 'text-[13px]'; const paddingY = dense ? 'py-1.5' : 'py-2'; const gapClass = dense ? 'gap-1' : 'gap-2'; return props.nodes.map((node) => { const isSelected = rowSelected(node, props.selectedRowRef); const labelIndent = node.kind === 'detail' ? node.level * 16 + 16 : node.level * 16; const canToggle = isSurfaceNode(node) && node.expandable; const nextSelection: StatementInspectorSelection = node.kind === 'surface' ? { kind: 'surface', key: node.row.key } : { kind: 'detail', key: node.row.key, parentKey: node.parentSurfaceKey }; return (
{canToggle ? ( ) : ( )}
{props.periods.map((period, index) => ( {props.renderCellValue(node.row, period.id, index > 0 ? props.periods[index - 1]?.id ?? null : null)} ))} {isSurfaceNode(node) && node.expanded ? ( <> Expanded children for {node.row.label} {renderNodes({ ...props, nodes: node.children, dense })} ) : null}
); }); } export function StatementMatrix(props: StatementMatrixProps) { const { dense = false, virtualized = false } = props; const tableClass = dense ? 'data-table-dense min-w-[960px]' : 'data-table min-w-[1040px]'; // Hooks must be called unconditionally const parentRef = useRef(null); const flatRows = useMemo(() => flattenSections(props.sections), [props.sections]); const virtualizer = useVirtualizer({ count: flatRows.length, getScrollElement: () => parentRef.current, estimateSize: (index) => { const item = flatRows[index]; if (item.isSectionHeader) return dense ? 32 : 40; if (item.node.kind === 'surface' && surfaceBadges(item.node as any).length > 0) return dense ? 44 : 56; return dense ? 36 : 48; }, overscan: 10, }); // Non-virtualized version (original implementation with dense support) if (!virtualized) { return (
{props.periods.map((period) => ( ))} {props.sections.map((section) => ( {section.label ? ( ) : null} {renderNodes({ ...props, nodes: section.nodes, dense })} ))}
Metric
{props.periodLabelFormatter(period.periodEnd ?? period.filingDate)} {period.filingType} · {period.periodLabel}
{section.label}
); } // Virtualized version for large datasets return (
{props.periods.map((period) => ( ))} {virtualizer.getVirtualItems().map((virtualRow) => { const item = flatRows[virtualRow.index]; if (item.isSectionHeader) { return ( ); } const node = item.node; const isSelected = rowSelected(node, props.selectedRowRef); const labelIndent = node.kind === 'detail' ? node.level * 16 + 16 : node.level * 16; const canToggle = isSurfaceNode(node) && node.expandable; const nextSelection: StatementInspectorSelection = node.kind === 'surface' ? { kind: 'surface', key: node.row.key } : { kind: 'detail', key: node.row.key, parentKey: node.parentSurfaceKey }; const buttonSize = dense ? 'size-7' : 'size-11'; const buttonClass = dense ? 'rounded' : 'rounded-lg'; const labelSize = dense ? 'text-[13px]' : 'text-sm'; const detailLabelSize = dense ? 'text-xs' : 'text-[13px]'; const paddingY = dense ? 'py-1.5' : 'py-2'; const gapClass = dense ? 'gap-1' : 'gap-2'; return ( {props.periods.map((period, index) => ( ))} ); })}
Metric
{props.periodLabelFormatter(period.periodEnd ?? period.filingDate)} {period.filingType} · {period.periodLabel}
{item.sectionLabel}
{canToggle ? ( ) : ( )}
{props.renderCellValue(node.row, period.id, index > 0 ? props.periods[index - 1]?.id ?? null : null)}
); }