/* 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 */}
{/* 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 && (
)}
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 && (
)}
{/* 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 });