import { DataTableView, KeyValueRow, Notice, SectionHeading, Spinner, StaticChartSurface, loadingText, unavailableText } from "../../../components"; import type { ProjectedChartPoint } from "../../../components/chart/core/data"; import type { StaticChartSurfaceProps } from "../../../components/chart/static"; import type { DataTableCell } from "../../../components/ui/data-table/types"; import { colors, priceColor } from "../../../theme/colors"; import { Box, Text } from "../../../ui"; import { formatCompactAmount, formatPercentRaw } from "../../../utils/format"; import { formatSignedCompact, formatWeight, renderBar } from "./display"; import type { SectorSortPreference, SectorTableColumn, SectorTableRow, } from "./sector-model"; export interface AnalyticsMetricRow { id: string; label: string; value: string; detail?: string; color?: string; } export function AnalyticsMetricsPanel({ summaryRows, riskRows, height, }: { summaryRows: AnalyticsMetricRow[]; riskRows: AnalyticsMetricRow[]; height: number; }) { return ( {summaryRows.map((row) => ( ))} {riskRows.length > 0 && ( <> {riskRows.map((row) => ( ))} )} ); } export function PortfolioHistorySection({ show, loading, error, width, height, points, palette, axisLabel, period, stale, formatAxisValue, }: { show: boolean; loading: boolean; error: string | null | undefined; width: number; height: number; points: ProjectedChartPoint[]; palette: StaticChartSurfaceProps["colors"]; axisLabel: string; period: string | undefined; stale: boolean | undefined; formatAxisValue: (value: number) => string; }) { if (show) { return ( <> {` Flex ${period ?? ""}${stale ? " - cached" : ""}`} ); } if (loading) { return ( ); } if (error) { return ( {`${unavailableText("Account history")} ${error}`} ); } if (points.length > 0) { return ( {`${points.filter((point) => Number.isFinite(point.close)).length >= 2 ? "Enlarge this pane to view account history." : "Account history needs at least two observations for a chart."}`} ); } return null; } const sectorRowKey = (row: SectorTableRow) => row.id; // Module-level so the table's memoized rows keep their identity across renders. function renderSectorCell(row: SectorTableRow, column: SectorTableColumn): DataTableCell { switch (column.id) { case "sector": return { text: row.sector }; case "weight": return { text: formatWeight(row.weight) }; case "value": return { text: formatCompactAmount(row.value ?? undefined) }; case "pnl": return { text: formatSignedCompact(row.pnl), color: row.pnl == null ? colors.textMuted : priceColor(row.pnl), }; case "return": return { text: row.returnPct == null ? "—" : formatPercentRaw(row.returnPct), color: row.returnPct == null ? colors.textMuted : priceColor(row.returnPct), }; case "bar": return { text: renderBar(row.weight, column.width), color: colors.textMuted, }; } } export function SectorAllocationTable({ focused, resetScrollKey, columns, rows, sort, selectedSectorId, onHeaderClick, onSelectSector, }: { focused: boolean; resetScrollKey: string; columns: SectorTableColumn[]; rows: SectorTableRow[]; sort: SectorSortPreference; selectedSectorId: string | null; onHeaderClick: (columnId: string) => void; onSelectSector: (sectorId: string) => void; }) { return ( focused={focused} selection={{ kind: "id", selectedId: selectedSectorId, getId: (row) => row.id, onChange: (id) => onSelectSector(id), }} resetScrollKey={resetScrollKey} columns={columns} items={rows} sortColumnId={sort.columnId} sortDirection={sort.direction} onHeaderClick={onHeaderClick} getItemKey={sectorRowKey} emptyStateTitle="No sector data available" emptyStateHint="Load profile data or add sectors to the portfolio positions." renderCell={renderSectorCell} /> ); }