Fullscreen strategy detail with 5-row equity pagination.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 10:01:52 +08:00
parent 63225f64de
commit 18fdfc6d0a
2 changed files with 88 additions and 29 deletions
+52 -10
View File
@@ -204,6 +204,8 @@ export default function MonitorPage() {
const [detailStats, setDetailStats] = useState<NodeStats | null>(null); const [detailStats, setDetailStats] = useState<NodeStats | null>(null);
const [detailStatsErr, setDetailStatsErr] = useState(""); const [detailStatsErr, setDetailStatsErr] = useState("");
const [detailStatsLoading, setDetailStatsLoading] = useState(false); const [detailStatsLoading, setDetailStatsLoading] = useState(false);
const [equityPage, setEquityPage] = useState(0);
const EQUITY_PAGE_SIZE = 5;
const applyNodes = useCallback((list: NodeCard[]) => { const applyNodes = useCallback((list: NodeCard[]) => {
cachedNodes = list; cachedNodes = list;
@@ -215,12 +217,14 @@ export default function MonitorPage() {
setDetailStats(null); setDetailStats(null);
setDetailStatsErr(""); setDetailStatsErr("");
setDetailStatsLoading(false); setDetailStatsLoading(false);
setEquityPage(0);
return; return;
} }
let cancelled = false; let cancelled = false;
setDetailStats(null); setDetailStats(null);
setDetailStatsErr(""); setDetailStatsErr("");
setDetailStatsLoading(true); setDetailStatsLoading(true);
setEquityPage(0);
apiFetch<NodeStats>(`/api/nodes/${detailId}/stats`) apiFetch<NodeStats>(`/api/nodes/${detailId}/stats`)
.then((s) => { .then((s) => {
if (!cancelled) setDetailStats(s); if (!cancelled) setDetailStats(s);
@@ -431,6 +435,18 @@ export default function MonitorPage() {
const detailLegs = Array.isArray(detail?.position?.legs) const detailLegs = Array.isArray(detail?.position?.legs)
? (detail!.position.legs as Record<string, unknown>[]) ? (detail!.position.legs as Record<string, unknown>[])
: []; : [];
const equityNewestFirst = detailStats?.equity_curve?.length
? [...detailStats.equity_curve].reverse()
: [];
const equityPageCount = Math.max(
1,
Math.ceil(equityNewestFirst.length / EQUITY_PAGE_SIZE),
);
const equityPageSafe = Math.min(equityPage, equityPageCount - 1);
const equityPageRows = equityNewestFirst.slice(
equityPageSafe * EQUITY_PAGE_SIZE,
equityPageSafe * EQUITY_PAGE_SIZE + EQUITY_PAGE_SIZE,
);
return ( return (
<div> <div>
@@ -742,7 +758,7 @@ export default function MonitorPage() {
</dd> </dd>
</div> </div>
</dl> </dl>
{detailStats.equity_curve?.length ? ( {equityNewestFirst.length ? (
<> <>
<h4 style={{ marginTop: "0.75rem" }}></h4> <h4 style={{ marginTop: "0.75rem" }}></h4>
<div className="legs-table-wrap"> <div className="legs-table-wrap">
@@ -754,10 +770,7 @@ export default function MonitorPage() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{[...detailStats.equity_curve] {equityPageRows.map((x) => (
.reverse()
.slice(0, 30)
.map((x) => (
<tr key={x.group_id}> <tr key={x.group_id}>
<td className="mono">{x.group_id}</td> <td className="mono">{x.group_id}</td>
<td className={pnlClass(x.realized_pnl)}> <td className={pnlClass(x.realized_pnl)}>
@@ -768,11 +781,40 @@ export default function MonitorPage() {
</tbody> </tbody>
</table> </table>
</div> </div>
{detailStats.equity_curve.length > 30 ? ( <div className="pager">
<p className="meta"> <span className="pager-meta">
30 {detailStats.equity_curve.length} {equityPageSafe + 1}/{equityPageCount} · {" "}
</p> {EQUITY_PAGE_SIZE} · {equityNewestFirst.length}{" "}
) : null}
</span>
<div className="pager-actions">
<button
type="button"
className="btn ghost"
disabled={equityPageSafe <= 0}
onClick={() =>
setEquityPage(Math.max(0, equityPageSafe - 1))
}
>
</button>
<button
type="button"
className="btn ghost"
disabled={equityPageSafe >= equityPageCount - 1}
onClick={() =>
setEquityPage(
Math.min(
equityPageCount - 1,
equityPageSafe + 1,
),
)
}
>
</button>
</div>
</div>
</> </>
) : ( ) : (
<p className="meta"></p> <p className="meta"></p>
+29 -12
View File
@@ -454,29 +454,46 @@ td {
.modal-backdrop { .modal-backdrop {
position: fixed; position: fixed;
inset: 0; inset: 0;
background: rgba(4, 8, 14, 0.72); background: rgba(4, 8, 14, 0.92);
display: grid; display: flex;
place-items: center;
padding: 16px;
z-index: 50; z-index: 50;
} }
.modal-panel { .modal-panel {
width: min(720px, 100%); width: 100%;
max-height: min(90vh, 900px); height: 100%;
max-height: none;
overflow: auto; overflow: auto;
background: var(--panel); background: var(--panel);
border: 1px solid var(--line); border: none;
border-radius: 12px; border-radius: 0;
padding: 18px; padding: 20px 24px 28px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 14px; gap: 16px;
} }
.modal-panel.running { .modal-panel.running {
border-color: rgba(60, 179, 113, 0.65); box-shadow: inset 0 0 0 2px rgba(60, 179, 113, 0.45);
box-shadow: 0 0 0 1px rgba(60, 179, 113, 0.2); }
.pager {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 10px;
}
.pager-meta {
color: var(--muted);
font-size: 0.85rem;
font-variant-numeric: tabular-nums;
}
.pager-actions {
display: flex;
gap: 8px;
} }
.modal-head { .modal-head {