From 618976bf5f5c04d4192f489cd1a01158d45fe035 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Fri, 30 Oct 2026 16:55:36 -0500 Subject: [PATCH] Dashboard: accounts list in name order, counting the numbers in names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The accounts table kept the broker's order until the Account header was clicked, and that sort compared names as plain text, so XXX10 came before XXX2. It now opens sorted by name, comparing numbers as numbers (XXX1, XXX2 … XXX10; Vest's padded -02 between -1 and -10), and the other columns break ties by name so equal rows hold still between syncs. Co-Authored-By: Claude Opus 5.5 --- app/page.tsx | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) 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