ui: tighten dashboard copy and fix layout issues
This commit is contained in:
@@ -238,43 +238,59 @@ export default function AnalyticsPageClient() {
|
||||
<span style={{ fontSize: 12, color: 'var(--ink-4)' }}>{accuracy.total_directional_signals} directional signals measured</span>
|
||||
<span style={{ fontSize: 11, color: 'var(--up)', fontWeight: 600, marginLeft: 'auto' }}>Public · no login needed</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))', gap: 8 }}>
|
||||
{/* Overall */}
|
||||
<div style={{ background: 'var(--surface-2)', borderRadius: 10, padding: '12px 14px' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.06em' }}>Overall</div>
|
||||
{(['m5','m15','m1h'] as const).map(w => {
|
||||
const d = accuracy.overall[w]
|
||||
const pct = d.accuracy_pct
|
||||
const color = pct >= 55 ? 'var(--up)' : pct >= 45 ? 'var(--ink-2)' : 'var(--down)'
|
||||
return (
|
||||
<div key={w} className="row between" style={{ marginBottom: 3 }}>
|
||||
<span style={{ fontSize: 11, color: 'var(--ink-3)' }}>{w.replace('m1','1').replace('m','')}</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 700, color, fontVariantNumeric: 'tabular-nums' }}>{pct != null ? `${pct.toFixed(0)}%` : '—'}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{Object.entries(accuracy.by_signal).map(([sig, data]) => {
|
||||
const label = sig === 'buy' ? '🟢 Buy' : sig === 'short' ? '🔴 Short' : '🟡 Sell'
|
||||
return (
|
||||
<div key={sig} style={{ background: 'var(--surface-2)', borderRadius: 10, padding: '12px 14px' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.06em' }}>{label} <span style={{ fontWeight: 400 }}>({data.count})</span></div>
|
||||
{(['m5','m15','m1h'] as const).map(w => {
|
||||
const d = data[w]
|
||||
if (d.checked < 2) return <div key={w} className="row between" style={{ marginBottom: 3 }}><span style={{ fontSize: 11, color: 'var(--ink-3)' }}>{w.replace('m1','1').replace('m','')}</span><span style={{ fontSize: 12, color: 'var(--ink-3)' }}>—</span></div>
|
||||
const pct = d.accuracy_pct
|
||||
const color = pct >= 55 ? 'var(--up)' : pct >= 45 ? 'var(--ink-2)' : 'var(--down)'
|
||||
return (
|
||||
<div key={w} className="row between" style={{ marginBottom: 3 }}>
|
||||
<span style={{ fontSize: 11, color: 'var(--ink-3)' }}>{w.replace('m1','1').replace('m','')}</span>
|
||||
<span style={{ fontSize: 13, fontWeight: 700, color, fontVariantNumeric: 'tabular-nums' }}>{pct.toFixed(0)}%</span>
|
||||
{(() => {
|
||||
// One full-width block per measurement window. Big overall number
|
||||
// + a hit-rate bar (50% tick = coin-flip baseline) + per-direction
|
||||
// breakdown. `repeat(3, 1fr)` stretches across the card so no
|
||||
// whitespace is stranded on wide screens.
|
||||
const WINDOWS = ['m5', 'm15', 'm1h'] as const
|
||||
const WINDOW_LABEL = { m5: 'After 5 min', m15: 'After 15 min', m1h: 'After 1 hour' } as const
|
||||
const pctColor = (pct: number) => pct >= 55 ? 'var(--up)' : pct >= 45 ? 'var(--ink-2)' : 'var(--down)'
|
||||
const fmtBreakdown = (pct: number | null, sparse: boolean) =>
|
||||
sparse || pct == null || Number.isNaN(pct) ? '—' : `${pct.toFixed(0)}%`
|
||||
// auto-fit, not repeat(3,1fr) — a hard 3-col grid overflowed the
|
||||
// viewport on phones and forced the whole page to scroll sideways.
|
||||
return (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 12 }}>
|
||||
{WINDOWS.map(w => {
|
||||
const pct = accuracy.overall[w].accuracy_pct
|
||||
const has = pct != null && !Number.isNaN(pct)
|
||||
return (
|
||||
<div key={w} style={{ background: 'var(--surface-2)', borderRadius: 10, padding: '14px 16px' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em', display: 'flex', alignItems: 'center' }}>
|
||||
{WINDOW_LABEL[w]}
|
||||
<InfoTip text={`Share of signals where price had moved in the signalled direction ${w === 'm5' ? '5 minutes' : w === 'm15' ? '15 minutes' : '1 hour'} after the post. 50% = coin flip.`} placement="top" />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="mono" style={{ fontSize: 30, fontWeight: 600, letterSpacing: '-0.01em', marginTop: 8, color: has ? pctColor(pct) : 'var(--ink-4)', fontVariantNumeric: 'tabular-nums' }}>
|
||||
{has ? `${pct.toFixed(0)}%` : '—'}
|
||||
</div>
|
||||
{/* hit-rate bar with a 50% coin-flip tick */}
|
||||
<div style={{ position: 'relative', height: 6, background: 'var(--surface-3)', borderRadius: 999, marginTop: 10, overflow: 'hidden' }}>
|
||||
{has && (
|
||||
<div style={{ position: 'absolute', inset: 0, width: `${Math.min(100, Math.max(0, pct))}%`, background: pctColor(pct), borderRadius: 999, opacity: 0.75 }} />
|
||||
)}
|
||||
<div style={{ position: 'absolute', left: '50%', top: 0, bottom: 0, width: 1.5, background: 'var(--ink-4)', opacity: 0.55 }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16, marginTop: 12, fontSize: 12, color: 'var(--ink-3)', flexWrap: 'wrap' }}>
|
||||
{Object.entries(accuracy.by_signal).map(([sig, d]) => {
|
||||
const sparse = d[w].checked < 2
|
||||
const v = fmtBreakdown(d[w].accuracy_pct, sparse)
|
||||
const label = sig === 'buy' ? '🟢 Buy' : sig === 'short' ? '🔴 Short' : '🟡 Sell'
|
||||
return (
|
||||
<span key={sig} style={{ whiteSpace: 'nowrap' }}>
|
||||
{label}{' '}
|
||||
<strong style={{ color: sparse ? 'var(--ink-4)' : 'var(--ink-2)', fontVariantNumeric: 'tabular-nums' }}>{v}</strong>
|
||||
<span style={{ color: 'var(--ink-4)' }}> ({d.count})</span>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -315,10 +331,21 @@ export default function AnalyticsPageClient() {
|
||||
<div className="hero-value mono" style={{ marginTop: 6, fontSize: 40 }}>
|
||||
{filteredTrades.length ? fmtMoney(summaryPnl) : '—'}
|
||||
</div>
|
||||
<div className="row gap-s" style={{ marginTop: 8 }}>
|
||||
<span className={`chip ${winRate >= 50 ? 'up' : 'down'}`}>{winRate.toFixed(1)}% {isZh ? '胜率' : 'win rate'}</span>
|
||||
<span className="chip">{isZh ? `${filteredTrades.length} 笔交易` : `${filteredTrades.length} trades`}</span>
|
||||
</div>
|
||||
{/* Win-rate chip only when there are trades to rate — a red
|
||||
"0.0% win rate" next to an em-dash P&L misreads as a losing
|
||||
record when the wallet simply has no data. */}
|
||||
{pricedTrades.length > 0 && (
|
||||
<div className="row gap-s" style={{ marginTop: 8 }}>
|
||||
<span className={`chip ${winRate >= 50 ? 'up' : 'down'}`}>{winRate.toFixed(1)}% {isZh ? '胜率' : 'win rate'}</span>
|
||||
</div>
|
||||
)}
|
||||
{filteredTrades.length === 0 && (
|
||||
<div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 12, lineHeight: 1.6, maxWidth: 520 }}>
|
||||
No closed bot trades in this window. P&L, win rate and the
|
||||
per-trade metrics below fill in once auto-trade executions close
|
||||
— or pick a longer window on the top right.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -339,15 +366,14 @@ export default function AnalyticsPageClient() {
|
||||
{/* Signal accuracy is now shown at the top of the page (public, no login needed).
|
||||
Removed duplicate block here to avoid showing the same data twice. */}
|
||||
|
||||
{!filteredTrades.length && (
|
||||
{/* Empty-state card renders only for states no other element explains:
|
||||
- "window empty but trades exist" → Performance hero's inline note
|
||||
- privateLocked → the unlock card at the top of the page */}
|
||||
{!filteredTrades.length && !privateLocked && (!isConnected || !address || !trades.length) && (
|
||||
<div className="card" style={{ padding: 60, textAlign: 'center', color: 'var(--ink-3)' }}>
|
||||
<p style={{ fontSize: 14 }}>
|
||||
{!isConnected || !address
|
||||
? (isZh ? '连接钱包以加载你的分析数据。' : 'Connect your wallet to load your analytics.')
|
||||
: privateLocked
|
||||
? (isZh ? '签名解锁后可查看你的真实业绩和交易历史。' : 'Sign once above to unlock your personal P&L and trade history.')
|
||||
: trades.length
|
||||
? (isZh ? `${period} 时间窗内还没有已平仓交易。` : `No trades closed in the ${period} window yet.`)
|
||||
: (isZh ? '还没有交易数据。机器人开始执行后,这里会自动出现统计。' : 'No trade data yet. The bot will populate analytics once it starts executing.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user