import { useState, useEffect, useRef } from 'react' import anime from 'animejs' const REC_COLOR = { 'STRONG BUY': 'var(--buy)', 'BUY': 'var(--buy)', 'HOLD': 'var(--hold)', 'SELL': 'var(--sell)', 'STRONG SELL':'var(--sell)', } const REC_BG = { 'STRONG BUY': 'rgba(0,232,122,0.12)', 'BUY': 'rgba(0,232,122,0.08)', 'HOLD': 'rgba(255,204,68,0.08)', 'SELL': 'rgba(255,68,102,0.08)', 'STRONG SELL':'rgba(255,68,102,0.12)', } function ScoreBar({ score }) { const pct = Math.round(((score + 1) / 2) * 100) const color = score > 0.1 ? 'var(--buy)' : score < -0.1 ? 'var(--sell)' : 'var(--hold)' return (
{score > 0 ? '+' : ''}{score.toFixed(2)}
) } export default function PortfolioView({ onAnalyse }) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const listRef = useRef(null) useEffect(() => { fetch('/portfolio') .then(r => { if (!r.ok) throw new Error('Failed to load portfolio'); return r.json() }) .then(d => { setData(d); setLoading(false) }) .catch(e => { setError(e.message); setLoading(false) }) }, []) useEffect(() => { if (!data) return anime({ targets: '.holding-row', translateY: [20, 0], opacity: [0, 1], delay: anime.stagger(60), easing: 'easeOutExpo', duration: 500, }) }, [data]) if (loading) return (
📊
Loading your portfolio…
) if (error) return (
⚠️ {error} — is the backend running?
) const { holdings, summary } = data return (
{/* Summary bar */}
{summary.total} Holdings
{summary.buy} Buy signals
{summary.hold} Hold signals
{summary.sell} Sell signals
{/* Holdings table */}
Ticker Sector Weight Signal Score Conf.
{holdings.map((h) => (
{h.ticker} {h.name?.split(' ').slice(0,3).join(' ')}
{h.sector} {h.pct_of_portfolio}% {h.recommendation}
= 60 ? 'var(--text)' : 'var(--text-muted)' }}> {h.confidence}%
))}

Signals are quick estimates — click → on any row for a full deep analysis.

) }