import React, { useEffect, useState } from 'react'; import { useSchema } from '../api/hooks'; import { useSessionStore } from '../store/session'; import { formatNumber, typeBadgeClass, typeLabel } from '../lib/format'; import type { ColumnInfo } from '../api/types'; interface Props { compact?: boolean; } export function SchemaView({ compact = false }: Props) { const { columns, rowCount } = useSessionStore(); const { fetch, loading } = useSchema(); const [localColumns, setLocalColumns] = useState([]); // Re-fetch schema when columns from the store change (e.g. after operations) useEffect(() => { loadSchema(); }, [columns.length, columns.map(c => c.name).join(',')]); const loadSchema = async () => { const data = await fetch(); if (data) { setLocalColumns(data.columns); useSessionStore.getState().setSchema(data.columns, data.row_count); } }; const displayColumns = localColumns.length > 0 ? localColumns : columns; // Only show numeric stat columns if any column has numeric stats const anyHasNumericStats = displayColumns.some( (col) => col.has_numeric_stats === true || col.column_type === 'numeric' ); const renderStatCell = (value: number | string | null | undefined, col: ColumnInfo) => { const isNumeric = col.has_numeric_stats === true || col.column_type === 'numeric'; // Non-numeric column: show subtle dash if (!isNumeric) { return —; } // Numeric column but value is null/undefined if (value == null) { return N/A; } // Numeric column with actual value: show the formatted value const numVal = typeof value === 'number' ? value : Number(value); if (isNaN(numVal)) { return N/A; } return {formatNumber(numVal)}; }; if (loading && displayColumns.length === 0) { return
Loading schema...
; } // Compact mode: card-based layout for sidebar (avoids wide table overflow) if (compact) { return (
{displayColumns.map((col) => { const isNumeric = col.has_numeric_stats === true || col.column_type === 'numeric'; return (
{col.name} {typeLabel(col.column_type)}
{formatNumber(col.count - col.null_count)} filled {formatNumber(col.unique_count)} unique {col.null_count > 0 && ( {formatNumber(col.null_count)} null )}
{isNumeric && (
{col.min != null && min {formatNumber(col.min)}} {col.max != null && max {formatNumber(col.max)}} {col.mean != null && avg {formatNumber(col.mean)}} {col.std != null && std {formatNumber(col.std)}}
)}
); })} {!anyHasNumericStats && (

No numeric columns — statistical summaries require numeric data.

)}
); } // Full table mode for cell view return (
{anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } {displayColumns.map((col) => { return ( {anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } {anyHasNumericStats && } ); })}
Column Type Non-Null UniqueMinMaxMeanStdMedianIQR
{col.name} {typeLabel(col.column_type)} {col.column_type} {formatNumber(col.count - col.null_count)} {formatNumber(col.unique_count)}{renderStatCell(col.min, col)}{renderStatCell(col.max, col)}{renderStatCell(col.mean, col)}{renderStatCell(col.std, col)}{renderStatCell(col.median, col)}{renderStatCell(col.iqr, col)}
{!anyHasNumericStats && (

No numeric columns found — statistical summaries require numeric data.

)}
); }