import { useMemo } from "react"; import { SectionHeading } from "../../../components"; import { colors } from "../../../theme/colors"; import { Box, Text, TextAttributes, useUiCapabilities } from "../../../ui"; import type { EarningsEvent } from "../../../types/data-provider"; import { buildEarningsDetail, formatGrowth, rangeBar, type EstimateBlock } from "./detail-model"; interface EarningsDetailViewProps { event: EarningsEvent; width: number; height: number; } function signColor(value: number | null): string { if (value == null) return colors.textDim; return value > 0 ? colors.positive : value < 0 ? colors.negative : colors.textDim; } /** * Where the consensus sits between the low and high estimates. The desktop * draws a real track and dot; box-drawing glyphs are for the terminal only. */ function EstimateRangeTrack({ position, width }: { position: number; width: number }) { const { nativePaneChrome } = useUiCapabilities(); if (nativePaneChrome) { return ( ); } return {` ${rangeBar(position, width)} `}; } /** One estimate: the consensus, where it sits in the range, and how it moved. */ function EstimatePanel({ block, width }: { block: EstimateBlock; width: number }) { const labelWidth = 14; const barWidth = Math.max(12, width - block.lowText.length - block.highText.length - 4); return ( {block.consensus ?? "no consensus"} {block.analysts != null ? ( {` ${block.analysts} ${block.analysts === 1 ? "analyst" : "analysts"}`} ) : null} {block.position != null ? ( {block.lowText} {block.highText} ) : block.lowText || block.highText ? ( {`range ${block.lowText || "?"} to ${block.highText || "?"}`} ) : null} {block.yearAgo || block.growth != null ? ( year ago {block.yearAgo ?? ""} {block.growth != null ? ( {`${block.yearAgo ? " " : ""}${formatGrowth(block.growth)}`} ) : null} ) : null} {block.trend.map((row) => ( {row.label} {row.value} {row.changeText ? {` ${row.changeText}`} : null} ))} {block.revisions.map((row) => { const net = row.up != null && row.down != null ? row.up - row.down : null; return ( {`revisions ${row.label}`} {`${row.up ?? "?"} up · ${row.down ?? "?"} down`} ); })} ); } /** * Everything the provider knows about one upcoming report, laid out instead * of squeezed into fifteen columns. The deeper surfaces (ticker, estimates, * calls, analysts) are the pane footer's `t` `e` `c` `a`, not a row here. */ export function EarningsDetailView({ event, width, height }: EarningsDetailViewProps) { const detail = useMemo(() => buildEarningsDetail(event), [event]); const inner = Math.max(20, width - 2); const wide = inner >= 76; const panelWidth = wide ? Math.floor((inner - 3) / 2) : inner; const facts = [ detail.date, detail.timing, detail.dateStatus ? `date ${detail.dateStatus}` : null, detail.period, ].filter((part): part is string => !!part); return ( {detail.date} {facts.length > 1 ? ( {` ${facts.slice(1).join(" · ")}`} ) : null} ); }