// Dashboard.jsx const PERIODS = [ { id: 'all', label: 'All Time' }, { id: 'year', label: 'This Year' }, { id: 'month', label: 'This Month' }, { id: 'week', label: 'This Week' }, ]; const DashboardPage = ({ trades, onAddTrade, onEditTrade }) => { const { useState } = React; const [period, setPeriod] = useState('all'); const [instrument, setInstrument] = useState('all'); const filtered = filterByPeriod(trades, period) .filter(t => instrument === 'all' || t.type === instrument); const closed = filtered.filter(t => t.status === 'closed'); const stats = computeStats(filtered); const curve = getEquityCurve(filtered); const monthly = getPnLByMonth(filtered); const recent = [...filtered].sort((a, b) => b.date.localeCompare(a.date)).slice(0, 6); // Extra stats const biggestWin = closed.length ? Math.max(...closed.map(t => t.pnl || 0)) : null; const biggestLoss = closed.length ? Math.min(...closed.map(t => t.pnl || 0)) : null; const avgHold = calcAvgHoldTime(filtered); const profitFactor = stats.avgLoss > 0 ? (stats.avgWin * stats.wins) / (stats.avgLoss * stats.losses) : null; // Equity curve SVG const W = 680, H = 140; const vals = curve.map(p => p.value); const minV = Math.min(...vals), maxV = Math.max(...vals); const rangeV = maxV - minV || 1; const curvePoints = curve.map((p, i) => { const x = (i / (curve.length - 1)) * W; const y = H - ((p.value - minV) / rangeV) * (H - 20) - 10; return `${x},${y}`; }).join(' '); const areaPoints = `0,${H} ${curvePoints} ${W},${H}`; const lastIsPos = stats.totalPnL >= 0; const lineColor = lastIsPos ? '#00d27a' : '#f04060'; const fillColor = lastIsPos ? 'rgba(0,210,122,0.08)' : 'rgba(240,64,96,0.08)'; // Monthly bar chart const barMax = Math.max(...monthly.map(m => Math.abs(m.pnl)), 1); return React.createElement('div', { style: { padding: '28px 32px', maxWidth: '1200px' } }, [ // Header React.createElement('div', { key: 'hdr', style: { marginBottom: '24px', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' } }, [ React.createElement('div', { key: 'left' }, [ React.createElement('h1', { key: 'h', style: { fontSize: '22px', fontWeight: 700, color: 'var(--text)', margin: 0 } }, 'Dashboard'), React.createElement('p', { key: 'sub', style: { fontSize: '13px', color: 'var(--muted)', marginTop: '4px' } }, 'Performance overview') ]), // Filters row React.createElement('div', { key: 'filter-row', style: { display: 'flex', gap: '8px' } }, [ // Instrument filter React.createElement('div', { key: 'inst', style: { display: 'flex', gap: '4px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '8px', padding: '4px' } }, [['all','All'],['stock','Stock'],['option','Option']].map(([id, lbl]) => React.createElement('button', { key: id, onClick: () => setInstrument(id), style: { padding: '6px 12px', fontSize: '12px', fontWeight: instrument === id ? 600 : 400, borderRadius: '6px', border: 'none', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', background: instrument === id ? 'var(--border)' : 'transparent', color: instrument === id ? 'var(--text)' : 'var(--muted)', transition: 'all 0.15s' } }, lbl) ) ), // Period filter React.createElement('div', { key: 'periods', style: { display: 'flex', gap: '4px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '8px', padding: '4px' } }, PERIODS.map(p => React.createElement('button', { key: p.id, onClick: () => setPeriod(p.id), style: { padding: '6px 14px', fontSize: '12px', fontWeight: period === p.id ? 600 : 400, borderRadius: '6px', border: 'none', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', background: period === p.id ? '#5588ff' : 'transparent', color: period === p.id ? '#fff' : 'var(--muted)', transition: 'all 0.15s' } }, p.label)) ) ]) ]), // Stat cards row 1 React.createElement('div', { key: 'cards', style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '14px', marginBottom: '14px' } }, [ React.createElement('div', { key: 'pnl', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '20px 22px', borderTop: `2px solid ${lastIsPos ? '#00d27a' : '#f04060'}` } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '8px' } }, 'Total P&L'), React.createElement(PnLValue, { key: 'v', value: stats.totalPnL, size: 'xl' }), React.createElement('div', { key: 's', style: { fontSize: '12px', color: 'var(--muted)', marginTop: '6px' } }, `${stats.total} closed trades`) ]), React.createElement('div', { key: 'wr', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '20px 22px', borderTop: '2px solid #5588ff' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '8px' } }, 'Win Rate'), React.createElement('div', { key: 'v', style: { fontSize: '28px', fontWeight: 700, color: 'var(--text)', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, `${(stats.winRate * 100).toFixed(1)}%`), React.createElement('div', { key: 's', style: { fontSize: '12px', color: 'var(--muted)', marginTop: '6px' } }, `${stats.wins}W · ${stats.losses}L`) ]), React.createElement('div', { key: 'rr', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '20px 22px', borderTop: '2px solid #a064ff' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '8px' } }, 'Avg Risk/Reward'), React.createElement('div', { key: 'v', style: { fontSize: '28px', fontWeight: 700, color: 'var(--text)', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, `${stats.rr.toFixed(2)}x`), React.createElement('div', { key: 's', style: { fontSize: '12px', color: 'var(--muted)', marginTop: '6px' } }, `Avg win $${stats.avgWin.toFixed(0)} · Avg loss $${stats.avgLoss.toFixed(0)}`) ]), React.createElement('div', { key: 'ar', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '20px 22px', borderTop: '2px solid #ffb83f' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '8px' } }, 'Avg R Multiple'), React.createElement('div', { key: 'v', style: { fontSize: '28px', fontWeight: 700, color: 'var(--text)', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, fmtR(stats.avgR)), React.createElement('div', { key: 's', style: { fontSize: '12px', color: 'var(--muted)', marginTop: '6px' } }, 'Per closed trade') ]), ]), // Stat cards row 2 React.createElement('div', { key: 'cards2', style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '14px', marginBottom: '24px' } }, [ React.createElement('div', { key: 'bw', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 22px', display: 'flex', alignItems: 'center', gap: '14px' } }, [ React.createElement('div', { key: 'icon', style: { width: '36px', height: '36px', borderRadius: '8px', background: 'rgba(0,210,122,0.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px', flexShrink: 0 } }, '↑'), React.createElement('div', { key: 'txt' }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '4px' } }, 'Biggest Win'), React.createElement('div', { key: 'v', style: { fontSize: '20px', fontWeight: 700, color: '#00d27a', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, biggestWin != null ? `+$${biggestWin.toFixed(2)}` : '—'), ]) ]), React.createElement('div', { key: 'bl', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 22px', display: 'flex', alignItems: 'center', gap: '14px' } }, [ React.createElement('div', { key: 'icon', style: { width: '36px', height: '36px', borderRadius: '8px', background: 'rgba(240,64,96,0.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px', flexShrink: 0 } }, '↓'), React.createElement('div', { key: 'txt' }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '4px' } }, 'Biggest Loss'), React.createElement('div', { key: 'v', style: { fontSize: '20px', fontWeight: 700, color: '#f04060', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, biggestLoss != null ? `-$${Math.abs(biggestLoss).toFixed(2)}` : '—'), ]) ]), React.createElement('div', { key: 'ht', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 22px', display: 'flex', alignItems: 'center', gap: '14px' } }, [ React.createElement('div', { key: 'icon', style: { width: '36px', height: '36px', borderRadius: '8px', background: 'rgba(85,136,255,0.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px', flexShrink: 0 } }, '⏱'), React.createElement('div', { key: 'txt' }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '4px' } }, 'Avg Hold Time'), React.createElement('div', { key: 'v', style: { fontSize: '18px', fontWeight: 700, color: '#5588ff', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1.2 } }, fmtHoldTime(avgHold)), ]) ]), React.createElement('div', { key: 'pf', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 22px', display: 'flex', alignItems: 'center', gap: '14px' } }, [ React.createElement('div', { key: 'icon', style: { width: '36px', height: '36px', borderRadius: '8px', background: 'rgba(160,100,255,0.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px', flexShrink: 0 } }, '∞'), React.createElement('div', { key: 'txt' }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '4px' } }, 'Profit Factor'), React.createElement('div', { key: 'v', style: { fontSize: '20px', fontWeight: 700, color: '#a064ff', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, profitFactor != null ? profitFactor.toFixed(2) : '—'), ]) ]), ]), // Equity curve React.createElement('div', { key: 'equity', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '22px 24px', marginBottom: '20px' } }, [ React.createElement('div', { key: 'title', style: { fontSize: '13px', fontWeight: 600, color: 'var(--text2)', marginBottom: '16px', textTransform: 'uppercase', letterSpacing: '0.06em' } }, 'Equity Curve'), React.createElement('div', { key: 'chart', style: { width: '100%', overflowX: 'hidden' } }, React.createElement('svg', { width: '100%', viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: 'none', style: { display: 'block', height: '130px' } }, [ React.createElement('defs', { key: 'd' }, React.createElement('linearGradient', { id: 'eqGrad', x1: 0, y1: 0, x2: 0, y2: 1 }, [ React.createElement('stop', { key: 's1', offset: '0%', stopColor: lineColor, stopOpacity: 0.3 }), React.createElement('stop', { key: 's2', offset: '100%', stopColor: lineColor, stopOpacity: 0 }) ]) ), React.createElement('polygon', { key: 'area', points: areaPoints, fill: 'url(#eqGrad)' }), React.createElement('polyline', { key: 'line', points: curvePoints, fill: 'none', stroke: lineColor, strokeWidth: 2, strokeLinejoin: 'round' }) ]) ), React.createElement('div', { key: 'labels', style: { display: 'flex', justifyContent: 'space-between', marginTop: '8px' } }, [ React.createElement('span', { key: 'start', style: { fontSize: '11px', color: 'var(--muted)' } }, curve[1]?.date ? fmtDate(curve[1].date) : ''), React.createElement('span', { key: 'end', style: { fontSize: '11px', color: 'var(--muted)' } }, 'Today') ]) ]), // Bottom row: monthly bars + recent trades React.createElement('div', { key: 'bottom', style: { display: 'grid', gridTemplateColumns: '340px 1fr', gap: '20px' } }, [ // Monthly P&L React.createElement('div', { key: 'monthly', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '22px 24px' } }, [ React.createElement('div', { key: 't', style: { fontSize: '13px', fontWeight: 600, color: 'var(--text2)', marginBottom: '20px', textTransform: 'uppercase', letterSpacing: '0.06em' } }, 'Monthly P&L'), React.createElement('div', { key: 'bars', style: { display: 'flex', flexDirection: 'column', gap: '10px' } }, monthly.map(m => { const isPos = m.pnl >= 0; const barPct = Math.abs(m.pnl) / barMax * 100; const [yr, mo] = m.month.split('-'); const label = new Date(+yr, +mo - 1, 1).toLocaleString('default', { month: 'short', year: '2-digit' }); return React.createElement('div', { key: m.month, style: { display: 'flex', alignItems: 'center', gap: '10px' } }, [ React.createElement('span', { key: 'l', style: { fontSize: '12px', color: 'var(--muted)', width: '50px', textAlign: 'right', fontFamily: 'JetBrains Mono, monospace' } }, label), React.createElement('div', { key: 'bar', style: { flex: 1, height: '6px', background: 'var(--surface2)', borderRadius: '3px', overflow: 'hidden' } }, React.createElement('div', { style: { height: '100%', width: `${barPct}%`, background: isPos ? '#00d27a' : '#f04060', borderRadius: '3px', transition: 'width 0.4s' } }) ), React.createElement(PnLValue, { key: 'v', value: m.pnl, size: 'sm' }) ]); }) ) ]), // Recent trades React.createElement('div', { key: 'recent', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '22px 24px' } }, [ React.createElement('div', { key: 'th', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' } }, [ React.createElement('div', { key: 't', style: { fontSize: '13px', fontWeight: 600, color: 'var(--text2)', textTransform: 'uppercase', letterSpacing: '0.06em' } }, 'Recent Trades'), ]), React.createElement('div', { key: 'rows', style: { display: 'flex', flexDirection: 'column', gap: '2px' } }, recent.map(t => React.createElement('div', { key: t.id, style: { display: 'grid', gridTemplateColumns: '90px 60px 70px 80px 1fr 80px', alignItems: 'center', gap: '12px', padding: '10px 12px', borderRadius: '6px', cursor: 'pointer', transition: 'background 0.15s' }, onMouseEnter: e => e.currentTarget.style.background = 'var(--surface2)', onMouseLeave: e => e.currentTarget.style.background = 'transparent', onClick: () => onEditTrade(t) }, [ React.createElement(SymbolCell, { key: 's', symbol: t.symbol }), React.createElement(TypeBadge, { key: 'type', type: t.type, optionType: t.optionType, strategy: t.strategy }), React.createElement(DirectionBadge, { key: 'dir', direction: t.direction }), React.createElement('span', { key: 'd', style: { fontSize: '12px', color: 'var(--muted)' } }, fmtDate(t.date)), React.createElement(StatusDot, { key: 'st', status: t.status }), React.createElement(PnLValue, { key: 'pnl', value: t.pnl }) ]) ) ) ]) ]) ]); }; Object.assign(window, { DashboardPage });