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.
)
}