/* ═══════════════════════════════════════════════════════════════════ MY HOLDINGS · owner-entered positions (qty + avg cost) Powers the "my avg / qty / value / P&L" personalization on every page. API-independent — works even though Bybit's private API is geo-blocked. ═══════════════════════════════════════════════════════════════════ */ function HoldingsModal({ open, onClose, coins, onSaved }) { const [rows, setRows] = React.useState({}); /* sym -> {qty, avg} */ const [saving, setSaving] = React.useState(false); const [query, setQuery] = React.useState(''); /* load existing holdings when opened */ React.useEffect(() => { if (!open) return; setQuery(''); ST_fetch('/holdings').then(d => { const map = {}; (d.holdings || []).forEach(h => { map[h.symbol] = { qty: String(h.qty), avg: String(h.avg_cost) }; }); setRows(map); }).catch(() => setRows({})); }, [open]); if (!open) return null; const list = (coins || ST_COINS).filter(c => !query || c.sym.toLowerCase().includes(query.toLowerCase()) || (c.name||'').toLowerCase().includes(query.toLowerCase()) ); /* held coins first */ list.sort((a, b) => (rows[b.sym] ? 1 : 0) - (rows[a.sym] ? 1 : 0)); const setRow = (sym, field, val) => setRows(prev => ({ ...prev, [sym]: { ...(prev[sym] || { qty: '', avg: '' }), [field]: val } })); const totalValue = list.reduce((s, c) => { const r = rows[c.sym]; const q = parseFloat(r?.qty || 0) || 0; return s + q * (c.price || 0); }, 0); const save = async () => { setSaving(true); const entries = Object.entries(rows); for (const [sym, r] of entries) { const qty = parseFloat(r.qty || 0) || 0; const avg = parseFloat(r.avg || 0) || 0; try { await fetch(window.ST_API + '/holdings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ symbol: sym, qty, avg_cost: avg }), }); } catch (_) {} } setSaving(false); if (onSaved) onSaved(); onClose(); }; return (