diff --git a/frontend/src/pages/accounts/AccountsPage.tsx b/frontend/src/pages/accounts/AccountsPage.tsx index 5e5d339..baa4cb9 100644 --- a/frontend/src/pages/accounts/AccountsPage.tsx +++ b/frontend/src/pages/accounts/AccountsPage.tsx @@ -45,12 +45,12 @@ const INVESTMENT_TYPES = ['inv_cd', 'inv_money_market', 'inv_treasury', 'inv_sav const isInvestmentType = (t: string) => INVESTMENT_TYPES.includes(t); +// Only show account types that represent real cash / financial positions. +// Income, expense, and equity accounts are internal bookkeeping — managed via +// budget import and system operations, not manually on this page. const ACCOUNT_TYPE_OPTIONS = [ - { value: 'asset', label: 'Asset' }, + { value: 'asset', label: 'Asset (Bank Account)' }, { value: 'liability', label: 'Liability' }, - { value: 'equity', label: 'Equity' }, - { value: 'income', label: 'Income' }, - { value: 'expense', label: 'Expense' }, { value: '__divider', label: '── Investment Accounts ──', disabled: true }, { value: 'inv_cd', label: 'Investment — CD' }, { value: 'inv_money_market', label: 'Investment — Money Market' }, @@ -161,7 +161,7 @@ export function AccountsPage() { accountNumber: '', name: '', description: '', - accountType: 'expense', + accountType: 'asset', fundType: 'operating', is1099Reportable: false, initialBalance: 0, @@ -390,9 +390,13 @@ export function AccountsPage() { }; // ── Filtering ── - // Hide system accounts (auto-created equity offsets) from the accounts view + // Only show asset and liability accounts — these represent real cash positions. + // Income, expense, and equity accounts are internal bookkeeping managed via + // budget import, transactions, and system operations. + const VISIBLE_ACCOUNT_TYPES = ['asset', 'liability']; const filtered = accounts.filter((a) => { if (a.is_system) return false; + if (!VISIBLE_ACCOUNT_TYPES.includes(a.account_type)) return false; if (search && !a.name.toLowerCase().includes(search.toLowerCase()) && !String(a.account_number).includes(search)) return false; if (filterType && a.account_type !== filterType) return false; if (filterFund && a.fund_type !== filterFund) return false; @@ -407,10 +411,11 @@ export function AccountsPage() { const reserveInvestments = investments.filter((i) => i.fund_type === 'reserve' && i.is_active); // ── Summary cards ── - // Exclude system accounts from summaries to avoid double-counting + // Only show asset and liability totals — these are real cash positions. + // Income/expense/equity are internal bookkeeping and don't belong here. const totalsByType = accounts.reduce( (acc, a) => { - if (a.is_active && !a.is_system) { + if (a.is_active && !a.is_system && VISIBLE_ACCOUNT_TYPES.includes(a.account_type)) { acc[a.account_type] = (acc[a.account_type] || 0) + parseFloat(a.balance || '0'); } return acc; @@ -418,14 +423,17 @@ export function AccountsPage() { {} as Record, ); - // Include investment principal in the asset totals + // Include investment current value in a separate summary card const investmentTotal = investments .filter((i) => i.is_active) .reduce((s, i) => s + parseFloat(i.current_value || i.principal || '0'), 0); if (investmentTotal > 0) { - totalsByType['asset (investments)'] = investmentTotal; + totalsByType['investments'] = investmentTotal; } + // Net position = assets + investments - liabilities + const netPosition = (totalsByType['asset'] || 0) + investmentTotal - (totalsByType['liability'] || 0); + // ── Adjust modal: current balance from trial balance ── const adjustCurrentBalance = adjustingAccount ? parseFloat( @@ -461,17 +469,27 @@ export function AccountsPage() { - - {Object.entries(totalsByType).map(([type, total]) => ( - - - {type} - - - {fmt(total)} - + + + Cash on Hand + {fmt(totalsByType['asset'] || 0)} + + {investmentTotal > 0 && ( + + Investments + {fmt(investmentTotal)} - ))} + )} + {(totalsByType['liability'] || 0) > 0 && ( + + Liabilities + {fmt(totalsByType['liability'] || 0)} + + )} + + Net Position + = 0 ? 'green' : 'red'}>{fmt(netPosition)} + @@ -485,7 +503,10 @@ export function AccountsPage() {