"use client"; import { useEffect, useState } from "react"; import type { AccountInfo, Position, PendingOrder } from "@/lib/mt5"; import { formatCurrency } from "@/lib/format"; import PositionsTable from "./PositionsTable"; import PendingOrdersTable from "./PendingOrdersTable"; const MAGIC = 19730; const POLL_MS = 30_000; interface State { account: AccountInfo | null; positions: Position[]; orders: PendingOrder[]; loading: boolean; error: string | null; ts: Date | null; } export default function LiveDashboard() { const [state, setState] = useState({ account: null, positions: [], orders: [], loading: true, error: null, ts: null, }); async function fetchData() { try { const [aRes, pRes, oRes] = await Promise.all([ fetch("/api/account"), fetch("/api/positions"), fetch("/api/orders"), ]); if (!aRes.ok) throw new Error("bridge error"); const [account, positions, orders] = await Promise.all([ aRes.json() as Promise, pRes.json() as Promise, oRes.json() as Promise, ]); setState({ account, positions, orders: Array.isArray(orders) ? orders : [], loading: false, error: null, ts: new Date(), }); } catch (e) { setState(prev => ({ ...prev, loading: false, error: String(e) })); } } useEffect(() => { fetchData(); const id = setInterval(fetchData, POLL_MS); return () => clearInterval(id); }, []); const aresPos = state.positions.filter(p => p.magic === MAGIC); const aresOrd = state.orders.filter(o => o.magic === MAGIC); const openPnl = aresPos.reduce((s, p) => s + p.profit, 0); const currency = state.account?.currency ?? "USD"; if (state.loading) return ; if (state.error) { return (
Unable to connect to MT5 bridge.
); } const { account } = state; if (!account) return null; return ( <> {/* Account stats */}

Account Overview

{state.ts && ( {state.ts.toLocaleTimeString()} )}
0 : undefined} />
{/* Open positions */}

Open Positions

{aresPos.length > 0 && ( {aresPos.length} )}
{aresPos.length === 0 ? ( No open positions right now. ) : (
)}
{/* Pending orders */}

Pending Orders

{aresOrd.length > 0 && ( {aresOrd.length} )}
{aresOrd.length === 0 ? ( No pending orders right now. ) : (
)}
); } // ── Sub-components ───────────────────────────────────────────────────────────── function StatTile({ label, value, diff, colored }: { label: string; value: string; diff?: { val: number; currency: string }; colored?: boolean; }) { return (

{label}

{value}

{diff && (

= 0 ? "text-bull" : "text-bear"}`}> {diff.val >= 0 ? "+" : ""}{formatCurrency(diff.val, diff.currency)}

)}
); } function Empty({ children }: { children: React.ReactNode }) { return (
{children}
); } function LoadingSkeleton() { return (
{[...Array(4)].map((_, i) => (
))}
); }