import { useState, type ReactElement } from 'react' import { useBridge, useData, usePublish, type PanelRef, type SourceData } from '@jaspers-ai/sdk' import { display as d } from './format' import { Bar, Empty, Loading, marketState, RangeBar, Section, Signed, SymbolField, usePanelStateValue, useStateWriter } from './shared' import { num, record, records, text, type Row } from './values' import { cleanError, isFund, overviewOutput, overviewStats, ratingAction, ratingMix, readSymbol, smallPercent } from './views' import './styles.css' // One company or fund on a page: the quote, the 52-week range, key statistics, the analysts' // targets and ratings, earnings, and the profile, and for a fund its holdings. A run per tool, each // drawn as it lands; a section with nothing to show stays out. export function OverviewView({ panel }: { panel: PanelRef }): ReactElement { const state = usePanelStateValue(panel) const write = useStateWriter(panel) if (state === undefined) return const symbol = readSymbol(state) return (
write([['symbol', next]])} /> Company
{symbol ? ( ) : ( Enter a symbol: AAPL, SHOP.TO, SPY. )}
) } function Overview({ panel, symbol }: { panel: PanelRef; symbol: string }): ReactElement { const quote = useData('yfinance/quote', { symbols: [symbol] }) const statistics = useData('yfinance/statistics', { symbol }) const analysts = useData('yfinance/analysts', { symbol, limit: 5 }) const earnings = useData('yfinance/earnings', { symbol }) const profile = useData('yfinance/profile', { symbol }) const output = overviewOutput(symbol, { quote, statistics, analysts, earnings, profile }) usePublish(panel, output) const q = quote.data?.[0] ?? {} const hint = num(q['priceHint']) return ( <>
{output.name ?? symbol} {[symbol, text(q['exchange']), output.currency].filter(Boolean).join(' · ')}
{quote.error ? (

{cleanError(quote.error)}

) : (
{quote.loading && output.price === null ? '…' : d.price(output.price, hint)} {marketState(output.marketState)}
)}
{isFund(output.type) && } ) } function Extended({ quote }: { quote: Row }): ReactElement | null { const pre = num(quote['preMarketPrice']) const post = num(quote['postMarketPrice']) const price = pre ?? post if (price === null) return null return ( {pre !== null ? 'Pre-market' : 'Post-market'} {d.price(price, num(quote['priceHint']))}{' '} ) } function Statistics({ run }: { run: SourceData }): ReactElement | null { const stats = overviewStats(run.data) if (!run.error && !run.loading && stats.length === 0) return null return (
{stats.map((s) => (
{s.label}
{s.value}
))}
) } /** A fund's figures, top holdings, and sector weights, from its own run. */ function Fund({ symbol }: { symbol: string }): ReactElement | null { const run = useData('yfinance/fund', { symbol }) const m = run.meta const figure = (key: string): number | null => num(m[key]) const cells: [string, string | null][] = [ ['Net assets', figure('totalAssets') !== null ? d.big(figure('totalAssets')) : null], ['Expense ratio', figure('expenseRatioPercent') !== null ? smallPercent(figure('expenseRatioPercent')) : null], ['Yield', figure('yieldPercent') !== null ? d.percent(figure('yieldPercent')) : null], ['NAV', figure('nav') !== null ? d.price(figure('nav')) : null], ['Year to date', figure('ytdReturnPercent') !== null ? d.signedPercent(figure('ytdReturnPercent')) : null], ['3-year average', figure('threeYearReturnPercent') !== null ? d.signedPercent(figure('threeYearReturnPercent')) : null], ['5-year average', figure('fiveYearReturnPercent') !== null ? d.signedPercent(figure('fiveYearReturnPercent')) : null], ['Beta (3 years)', figure('beta3Year') !== null ? d.fixed(figure('beta3Year')) : null], ['Turnover', figure('turnoverPercent') !== null ? d.percent(figure('turnoverPercent')) : null], ['Category', text(m['category'])], ['Family', text(m['family'])], ['Inception', text(m['inception'])], ] const shown = cells.filter((cell): cell is [string, string] => cell[1] !== null) const holdings = run.data ?? [] const sectors = records(m['sectors']) const widest = Math.max(0, ...sectors.map((s) => num(s['percent']) ?? 0)) if (!run.error && !run.loading && shown.length === 0 && holdings.length === 0) return null return (
{shown.map(([label, value]) => (
{label}
{value}
))}
{holdings.length > 0 && ( {holdings.map((h, i) => ( ))}
Top holdings Weight
{text(h['symbol'])} {text(h['name'])} {d.percent(num(h['percent']))}
)} {sectors.length > 0 && (
Sectors
{sectors.map((s) => { const weight = num(s['percent']) ?? 0 return (
{text(s['sector'])} {d.percent(weight)}
) })}
)}
) } function Analysts({ run, price, hint }: { run: SourceData; price: number | null; hint: number | null }): ReactElement | null { const target = record(run.meta['target']) const recommendation = record(run.meta['recommendation']) const month = records(run.meta['trend'])[0] const mix = ratingMix(month) const changes = run.data ?? [] const mean = num(target?.['mean']) if (!run.error && !run.loading && mean === null && mix.length === 0 && changes.length === 0) return null const key = text(recommendation?.['key']) const score = num(recommendation?.['mean']) const count = num(target?.['analysts']) const aside = [key && key.replace(/_/g, ' '), score !== null && `${d.fixed(score)} of 5`, count !== null && `${count} analysts`].filter(Boolean).join(' · ') const low = num(target?.['low']) const high = num(target?.['high']) const span = [low, high, price].filter((v): v is number => v !== null) return (
{mean !== null && low !== null && high !== null && ( <>

price mean target

)} {mix.length > 0 && ( <>
`${m.label} ${m.count}`).join(', ')}> {mix.map((m) => ( ))}

{mix.map((m) => ( {m.label} {m.count} ))}

)} {changes.length > 0 && ( {changes.map((c, i) => ( ))}
{text(c['date'])} {text(c['firm'])} {ratingAction(text(c['action']), text(c['from']), text(c['to']))} {num(c['target']) !== null ? d.price(num(c['target']), hint) : ''} {num(c['priorTarget']) !== null && num(c['priorTarget']) !== num(c['target']) && ( from {d.price(num(c['priorTarget']), hint)} )}
)}
) } function Earnings({ run }: { run: SourceData }): ReactElement | null { const next = record(run.meta['next']) const quarters = run.data ?? [] if (!run.error && !run.loading && !next && quarters.length === 0) return null const date = text(next?.['date']) const aside = date ? `next ${date}${next?.['estimated'] === true ? ' (estimated)' : ''}` : 'next date not announced' const eps = num(next?.['epsAverage']) const revenue = num(next?.['revenueAverage']) return (
{(eps !== null || revenue !== null) && (

Expected: {eps !== null && <>EPS {d.fixed(eps)}} {eps !== null && revenue !== null && ', '} {revenue !== null && <>revenue {d.big(revenue)}}

)} {quarters.length > 0 && ( {quarters.map((q) => ( ))}
Quarter EPS estimate Actual Surprise
{text(q['fiscalQuarter']) ?? text(q['quarter'])} {text(q['reported']) ? `reported ${text(q['reported'])}` : `ended ${text(q['quarter'])}`} {d.fixed(num(q['epsEstimate']))} {d.fixed(num(q['epsActual']))}
)}
) } function Profile({ run }: { run: SourceData }): ReactElement | null { const bridge = useBridge() const [open, setOpen] = useState(false) const meta = run.meta const summary = text(meta['summary']) const website = text(meta['website']) const employees = num(meta['employees']) const facts = [text(meta['sector']), text(meta['industry']), employees !== null && `${d.fixed(employees, 0)} employees`].filter(Boolean).join(' · ') const officers = (run.data ?? []).slice(0, 3) if (!run.error && !run.loading && !summary && !facts) return null return (
{facts &&

{facts}

} {(text(meta['address']) || website) && (

{text(meta['address'])} {website?.startsWith('https://') && ( <> {text(meta['address']) && ' · '} )}

)} {officers.length > 0 && (

{officers.map((o) => [text(o['name']), text(o['title'])].filter(Boolean).join(', ')).join(' · ')}

)} {summary && ( <>

{summary}

)}
) }