diff --git a/app/page.tsx b/app/page.tsx index 4ea9fbf..8810ddc 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -21,6 +21,11 @@ function statusRank(account: AccountState): number { return 3; // In Trade } +/** Account names in counting order: XXX2 before XXX10, not after it. */ +function byName(a: AccountState, b: AccountState): number { + return a.name.localeCompare(b.name, undefined, { numeric: true }); +} + function fmt(value: number) { return value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } @@ -98,7 +103,7 @@ function maskName(name: string, privacy: boolean): string { function SortHeader({ label, col, sortKey, sortDir, onSort }: { label: string; col: SortKey; - sortKey: SortKey | null; + sortKey: SortKey; sortDir: SortDir; onSort: (col: SortKey) => void; }) { @@ -527,7 +532,7 @@ function FirmRows({ state, firm, hideDead, privacy, sortKey, sortDir, tradeSymbo firm: FirmConfig; hideDead: boolean; privacy: boolean; - sortKey: SortKey | null; + sortKey: SortKey; sortDir: SortDir; tradeSymbol: string; onChanged: () => void; @@ -554,20 +559,22 @@ function FirmRows({ state, firm, hideDead, privacy, sortKey, sortDir, tradeSymbo } } - const sortedAccounts = sortKey === null ? state.accounts : [...state.accounts].sort((a, b) => { + // Ties on any other column fall back to name order, so equal rows don't + // shuffle with the broker's list between syncs. + const sortedAccounts = [...state.accounts].sort((a, b) => { const dir = sortDir === 'asc' ? 1 : -1; switch (sortKey) { - case 'name': return dir * a.name.localeCompare(b.name); - case 'balance': return dir * (a.amount - b.amount); - case 'dayPnL': return dir * (a.realizedPnL - b.realizedPnL); - case 'daysTraded': return dir * (a.daysTraded - b.daysTraded); + case 'name': return dir * byName(a, b); + case 'balance': return dir * (a.amount - b.amount) || byName(a, b); + case 'dayPnL': return dir * (a.realizedPnL - b.realizedPnL) || byName(a, b); + case 'daysTraded': return dir * (a.daysTraded - b.daysTraded) || byName(a, b); case 'target': { const at = a.effectiveProfitTarget ?? a.accountType?.profitTarget ?? 0; const bt = b.effectiveProfitTarget ?? b.accountType?.profitTarget ?? 0; - return dir * (at - bt); + return dir * (at - bt) || byName(a, b); } - case 'status': return dir * (statusRank(a) - statusRank(b)); - default: return 0; + case 'status': return dir * (statusRank(a) - statusRank(b)) || byName(a, b); + default: return byName(a, b); } }); @@ -732,7 +739,8 @@ export default function Home() { const [catalog, setCatalog] = useState<{ lastSyncAt: string | null; lastError: string | null; masterUrl: string | null } | null>(null); const [hideDead, setHideDead] = useState(false); const [privacy, setPrivacy] = useState(false); - const [sortKey, setSortKey] = useState(null); + // Name order until a header is clicked, so XXX1 always sits above XXX2. + const [sortKey, setSortKey] = useState('name'); const [sortDir, setSortDir] = useState('asc'); // Trade controls