/* FinPulse — Stock detail (demo + live via MarketService). */ const TIME_TABS = ['1D', '5D', '1M', '3M', '6M', '1Y', '5Y']; function AnalystBar({ rec }) { if (!rec || !rec.total) return null; const segments = [ { k: 'strongBuy', label: 'Strong Buy', c: 'var(--up)' }, { k: 'buy', label: 'Buy', c: '#22c55e' }, { k: 'hold', label: 'Hold', c: 'var(--cat-economy)' }, { k: 'sell', label: 'Sell', c: '#f97316' }, { k: 'strongSell', label: 'Strong Sell', c: 'var(--down)' }, ]; return (

Analyst Consensus

{rec.total} analysts · {rec.period || 'latest'}
{segments.map((s) => rec[s.k] > 0 && (
))}
{segments.filter((s) => rec[s.k] > 0).map((s) => ( {s.label} {rec[s.k]} ))}
); } function StockScreen({ stock, onOpenStock, onBack, onOpenArticle }) { const L = useLive(); const [s, setS] = useState(stock); const [range, setRange] = useState('1D'); const [refreshing, setRefreshing] = useState(false); const [liveNews, setLiveNews] = useState(null); const [rec, setRec] = useState(null); const [peers, setPeers] = useState([]); const [insights, setInsights] = useState(null); useEffect(() => { setS(stock); setRange(stock && stock.live ? 'LIVE' : '1D'); setLiveNews(null); setRec(null); setPeers([]); setInsights(null); }, [stock]); useEffect(() => { if (!s || !s.live) return; const isUS = s.market !== 'IN' && !/\.NS$/i.test(s.symbol); if (isUS && L.hasKey) { L.market.getCompanyNews(s.symbol).then((items) => { if (window.NewsCache) window.NewsCache.putMany(items); setLiveNews(items); }).catch(() => setLiveNews([])); L.market.getRecommendations(s.symbol).then(setRec).catch(() => setRec(null)); L.market.getPeers(s.symbol).then(setPeers).catch(() => setPeers([])); L.market.getStockInsights(s.symbol).then(setInsights).catch(() => setInsights(null)); } else if (isUS) { setLiveNews([]); setRec(null); setPeers([]); setInsights(null); } else if (L.hasKey) { const sym = s.symbol || (s.ticker + '.NS'); L.market.getCompanyNews(sym).then((items) => { if (window.NewsCache) window.NewsCache.putMany(items); setLiveNews(items); }).catch(() => setLiveNews([])); } }, [s && s.id, L.hasKey]); if (!s) return null; if (!s.live) return ; const live = s.live; const C = curSym(s.cur); const up = s.chg >= 0; const data = live ? s.history.LIVE : s.history[range]; async function refresh() { if (!live) return; setRefreshing(true); try { setS(await L.market.refreshStock(s)); L.refreshRateLimit(); } catch (e) {} finally { setRefreshing(false); } } const dash = (v) => (v == null || v === 0 || Number.isNaN(v) ? '—' : v); const metrics = [ { label: 'Market Cap', value: s.mcap != null ? mcapM(s.mcap, s.cur) : '—', sub: s.sector || s.exchange || '—', color: s.sectorColor }, { label: 'P/E Ratio', value: s.pe != null ? Number(s.pe).toFixed(1) : '—', sub: 'TTM', color: 'var(--accent)' }, { label: '52W High', value: s.hi52 != null ? C + fmt.price(s.hi52) : '—', sub: s.hi52 ? ((1 - s.price / s.hi52) * 100).toFixed(1) + '% below' : '', color: 'var(--up)' }, { label: '52W Low', value: s.lo52 != null ? C + fmt.price(s.lo52) : '—', sub: s.lo52 ? ((s.price / s.lo52 - 1) * 100).toFixed(1) + '% above' : '', color: 'var(--down)' }, ]; const stats = [ ['Open', s.open != null ? C + fmt.price(s.open) : '—'], ['Prev Close', s.prevClose != null ? C + fmt.price(s.prevClose) : '—'], ['Day High', s.dayHigh != null ? C + fmt.price(s.dayHigh) : '—'], ['Day Low', s.dayLow != null ? C + fmt.price(s.dayLow) : '—'], ['Volume', s.vol != null ? fmt.vol(s.vol) + (s.market === 'IN' ? ' sh' : '') : '—'], ['52W Range', s.lo52 != null && s.hi52 != null ? C + fmt.price(s.lo52) + ' – ' + C + fmt.price(s.hi52) : '—'], ['Dividend Yld', s.div != null ? Number(s.div).toFixed(2) + '%' : '—'], ['Beta', s.beta != null ? Number(s.beta).toFixed(2) : '—'], ['Exchange', s.exchange || '—'], ['Currency', s.cur || '—'], ]; const related = liveNews || []; const similar = peers; return (
{live && s.logo ? { e.target.style.display = 'none'; }} /> : }

{s.name}

{s.sector && {s.sector}}
{live ? `${s.exchange || 'Listed'} · ${s.ticker}` : `NSE: ${s.ticker} · BSE: ${500000 + s.id * 13}`}
{C}{fmt.price(s.price)} {(s.change >= 0 ? '+' : '') + fmt.price(s.change)} ({fmt.pct(s.chg)}) {live && }
{live ? : TIME_TABS.map((t) => ( ))}
{live &&
Indicative line from live OHLC. Intraday candle history requires a premium Finnhub plan.
}
{metrics.map((m, i) => )}
{live && rec && } {live && insights && insights.priceTarget && (

Analyst Price Target

{insights.priceTarget.analysts || '—'} analysts
)} {live && insights && insights.earnings && insights.earnings.length > 0 && (

Earnings Surprises

{insights.earnings.map((e) => ( ))}
PeriodActualEstimateSurprise
{e.period} {e.actual != null ? e.actual : '—'} {e.estimate != null ? e.estimate : '—'} = 0 ? 'var(--up)' : 'var(--down)' }}> {e.surprisePct != null ? fmt.pct(e.surprisePct) : '—'}
)} {live && insights && insights.upgrades && insights.upgrades.length > 0 && (

Upgrades & Downgrades

{insights.upgrades.map((u) => (
{u.company} · {u.fromGrade || '—'} → {u.toGrade || u.action} {u.date}
))}
)} {!live && (

Volume · 22 sessions

in lakh shares
)}

Key Statistics

{stats.map((kv, i) => (
{kv[0]}{dash(kv[1])}
))}

About

{live ? `${s.name} (${s.ticker}) is listed on ${s.exchange || 'a major exchange'} in the ${s.sector} sector. Live data via Finnhub.` : `${s.name} is a leading ${s.sector.toLowerCase()} company listed on the NSE and BSE, part of India's benchmark indices and tracked closely by institutional and retail investors.`}

{insights && insights.sentiment && (
News sentiment: {insights.sentiment.sentiment === 'bull' ? 'Bullish' : 'Bearish'} {insights.sentiment.bullish != null ? ` (${insights.sentiment.bullish}% bull / ${insights.sentiment.bearish}% bear)` : ''}
)}
{live && s.web ? Website : }

{live ? 'Company News' : 'Related News'}

{related.length ? :
No recent news.
} {insights && insights.press && insights.press.length > 0 && ( <>

Press Releases

)} {insights && insights.dividends && insights.dividends.length > 0 && ( <>

Recent Dividends

{insights.dividends.map((d) => (
{d.date} {d.currency || '$'}{d.amount}
))} )}
{similar.length > 0 && (

Peers in {s.sector}

{similar.map((x) => (
onOpenStock(x)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0', cursor: 'pointer', borderBottom: '1px solid var(--border)' }}> {x.name}
))}
)}
); } Object.assign(window, { StockScreen });