/* Dashboard widgets — Portfolio donut (WorldMap ausgelagert → _world-map-widget.jsx.disabled) */ const { useState: useStateW, useMemo: useMemoW, useEffect: useEffectW } = React; /* ============================================================ IDEAL PORTFOLIO — GeoStrat Engine only ============================================================ */ /* ---------- Donut chart helpers ---------- */ function donutPath(cx, cy, R, r, startFrac, endFrac) { const τ = 2 * Math.PI; const toXY = (frac, radius) => [ cx + radius * Math.cos(frac * τ - Math.PI / 2), cy + radius * Math.sin(frac * τ - Math.PI / 2), ]; const [x1, y1] = toXY(startFrac, R); const [x2, y2] = toXY(endFrac, R); const [x3, y3] = toXY(endFrac, r); const [x4, y4] = toXY(startFrac, r); const large = endFrac - startFrac > 0.5 ? 1 : 0; return `M${x1},${y1} A${R},${R},0,${large},1,${x2},${y2} L${x3},${y3} A${r},${r},0,${large},0,${x4},${y4} Z`; } // Farbpalette nach Direction function sliceColor(direction, index) { const d = (direction || '').toLowerCase(); if (d.startsWith('short')) return '#C4883A'; // amber if (d.includes('vol')) return '#7CB9C4'; // teal — long_vol if (d.includes('underweight')) return '#8B6A3A'; // dunkles amber const longs = ['#FFFFFF', '#D4D4D4', '#AAAAAA', '#888888', '#666666', '#555555']; return longs[index % longs.length]; } function PortfolioDonut({ positions }) { const [hovered, setHovered] = useStateW(null); if (!positions || positions.length === 0) return null; // Normalisiere auf 100 % (falls Positionen nicht exakt 100 ergeben) const total = positions.reduce((s, p) => s + (p.allocation_pct || 0), 0); const norm = total > 0 ? 100 / total : 1; const cx = 80, cy = 80, R = 68, r = 44; let cursor = 0; const slices = positions.map((p, i) => { const frac = ((p.allocation_pct || 0) * norm) / 100; const slice = { p, i, startFrac: cursor, endFrac: cursor + frac }; cursor += frac; return slice; }); const hov = hovered != null ? positions[hovered] : null; return (
{/* SVG Donut */}
{slices.map(({ p, i, startFrac, endFrac }) => ( setHovered(i)} onMouseLeave={() => setHovered(null)} /> ))} {/* Zentrierter Text */} 8 ? 9 : 12, fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', fontWeight: 600 }}> {hov ? (hov.ticker || hov.asset || '—') : `${positions.length}`} {hov ? `${(hov.allocation_pct || 0).toFixed(0)}%` : 'POSITIONEN'}
{/* Legende */}
{slices.map(({ p, i }) => (
setHovered(i)} onMouseLeave={() => setHovered(null)} >
{p.ticker || p.asset || '—'}
{p.asset_class && (
{p.asset_class}
)}
{(p.allocation_pct || 0).toFixed(0)}%
))}
); } /* ---------- Conviction stars renderer ---------- */ function ConvictionStars({ value, max = 5 }) { return ( {'★'.repeat(value)}{'☆'.repeat(max - value)} ); } /* ---------- Direction badge ---------- */ function DirectionBadge({ direction }) { const d = (direction || '').toLowerCase(); const cfg = d.startsWith('short') ? { symbol: '↓', color: '#E5B25C', label: 'SHORT' } : d === 'long_vol' ? { symbol: '↗', color: '#7CB9C4', label: 'LONG VOL' } : d.includes('underweight') ? { symbol: '↘', color: '#8B6A3A', label: 'UW' } : d.startsWith('long') ? { symbol: '↑', color: '#FFFFFF', label: 'LONG' } : { symbol: '—', color: 'var(--on-surface-quiet)', label: 'NEUTR' }; return ( {cfg.symbol} {cfg.label} ); } /* ---------- Agent position list (GeoStrat tab) ---------- */ function AgentPositionList({ positions, simId }) { if (!positions) { return (
GeoStrat-Empfehlung wird geladen…
); } if (positions.length === 0) { return (
Noch keine Agent-Empfehlung verfügbar.

Daten werden beim nächsten Agent-Run generiert (tägl. 00:00 + 12:00 UTC) oder nach einer manuell gestarteten Simulation.

); } return (
Richtung
Position
Conv.
Horizont
{positions.slice(0, 9).map((p, i) => ( {/* Richtung + Allokation */}
{p.allocation_pct != null && ( {p.allocation_pct}% )}
{/* Ticker + Instrument-Name + Agent */}
{p.ticker || p.asset || '—'}
{(p.instrument_full_name || p.asset_class) && (
{p.instrument_full_name || p.asset_class.toUpperCase()}
)} {p.source_agent && (
{p.source_agent}
)}
{/* Conviction */}
{/* Horizont */}
{p.time_horizon_days ? `${p.time_horizon_days}d` : '—'}
))}
{simId && (
Vollständige Simulation öffnen →
)}
Automatisch generiert · GeoStrat-Engine · Keine Anlageberatung
); } function PortfolioWidget() { const [data, setData] = useStateW(null); const [loading, setLoading] = useStateW(true); const [error, setError] = useStateW(null); const [refreshing, setRefreshing] = useStateW(false); const load = async (refresh = false) => { if (refresh) setRefreshing(true); else setLoading(true); setError(null); try { const res = await window.RICS_API.getCombinedPortfolio(refresh); setData(res); } catch (e) { setError(e.message); } finally { setLoading(false); setRefreshing(false); } }; useEffectW(() => { if (!window.RICS_API) { setLoading(false); return; } load(); }, []); const fmtAge = (iso) => { if (!iso) return null; const diff = Date.now() - new Date(iso).getTime(); const min = Math.floor(diff / 60000); if (min < 60) return `vor ${min} Min.`; const h = Math.floor(min / 60); if (h < 24) return `vor ${h} Std.`; return `vor ${Math.floor(h / 24)} Tagen`; }; const positions = data?.positions || []; return (
GeoStrat · Konsolidiertes Portfolio
{data?.dominant_theme && (
{data.dominant_theme.slice(0, 120)}{data.dominant_theme.length > 120 ? '…' : ''}
)}
{/* Metazeile */} {data && (
{fmtAge(data.generated_at)} {data.cached && · Aus Cache} {positions.length > 0 && · {positions.length} Positionen}
)} {/* Loading */} {loading && (
Portfolio wird generiert…
)} {/* Fehler */} {!loading && error && (
{error}
)} {/* Leer */} {!loading && !error && positions.length === 0 && (
Keine Live-Reports verfügbar. Im Admin-Panel Reports veröffentlichen.
)} {/* Donut + Legende */} {!loading && positions.length > 0 && ( )} {/* Positionstabelle */} {!loading && positions.length > 0 && ( )} {/* Key Risk */} {!loading && data?.key_risk && (
Key Risk
{data.key_risk.slice(0, 240)}{data.key_risk.length > 240 ? '…' : ''}
)} {/* Disclaimer */} {!loading && positions.length > 0 && (
Automatisch generiert · GeoStrat-Engine · Keine Anlageberatung
)}
); } Object.assign(window, { PortfolioWidget });