Add exclude from budget tracking option per account
Accounts (e.g. mortgage) can be toggled to exclude from budget tracking: - Their transactions don't reduce envelope spending - Their balance is excluded from Available to Budget - Account card shows "excluded from budget" badge when enabled Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f38fceefba
commit
a4cde09d48
2 files changed
+27
-6
No files matched your search
+9
-6
@@ -30,6 +30,7 @@ interface Account {
|
|||||||
accountNumber?: string;
|
accountNumber?: string;
|
||||||
color: string;
|
color: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
excludeFromBudget?: boolean;
|
||||||
defaultPaycheckAmount?: number;
|
defaultPaycheckAmount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,13 +145,15 @@ export default function BudgetDashboard() {
|
|||||||
|
|
||||||
// ── Derived helpers ──────────────────────────────────────────────────────────
|
// ── Derived helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const calculateEnvelopeSpending = useCallback((envs: Envelope[], txns: Transaction[]): Envelope[] =>
|
const calculateEnvelopeSpending = useCallback((envs: Envelope[], txns: Transaction[], accs?: Account[]): Envelope[] => {
|
||||||
envs.map(envelope => {
|
const excludedIds = new Set((accs ?? accounts).filter(a => a.excludeFromBudget).map(a => a.id));
|
||||||
|
return envs.map(envelope => {
|
||||||
const spent = txns
|
const spent = txns
|
||||||
.filter(t => t.envelopeId === envelope.id && t.amount < 0)
|
.filter(t => t.envelopeId === envelope.id && t.amount < 0 && !excludedIds.has(t.accountId))
|
||||||
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
|
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
|
||||||
return { ...envelope, spent };
|
return { ...envelope, spent };
|
||||||
}), []);
|
});
|
||||||
|
}, [accounts]);
|
||||||
|
|
||||||
const calculateAccountBalances = useCallback((accs: Account[], txns: Transaction[]): Account[] => {
|
const calculateAccountBalances = useCallback((accs: Account[], txns: Transaction[]): Account[] => {
|
||||||
const map = new Map<string, number>();
|
const map = new Map<string, number>();
|
||||||
@@ -929,10 +932,10 @@ export default function BudgetDashboard() {
|
|||||||
return e.allocated + incomeAlloc > 0 && e.spent > e.allocated + incomeAlloc;
|
return e.allocated + incomeAlloc > 0 && e.spent > e.allocated + incomeAlloc;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Available to budget = all asset accounts minus total envelope allocations
|
// Available to budget = all asset accounts (excluding budget-excluded ones) minus total envelope allocations
|
||||||
const LIABILITY_TYPES = ['credit_card', 'mortgage', 'loan'];
|
const LIABILITY_TYPES = ['credit_card', 'mortgage', 'loan'];
|
||||||
const liquidBalance = accounts
|
const liquidBalance = accounts
|
||||||
.filter(a => !LIABILITY_TYPES.includes(a.type))
|
.filter(a => !LIABILITY_TYPES.includes(a.type) && !a.excludeFromBudget)
|
||||||
.reduce((s, a) => s + a.balance, 0);
|
.reduce((s, a) => s + a.balance, 0);
|
||||||
const totalAllocated = envelopes.reduce((s, e) => s + e.allocated, 0);
|
const totalAllocated = envelopes.reduce((s, e) => s + e.allocated, 0);
|
||||||
const availableToBudget = liquidBalance - totalAllocated;
|
const availableToBudget = liquidBalance - totalAllocated;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface Account {
|
|||||||
accountNumber?: string;
|
accountNumber?: string;
|
||||||
color: string;
|
color: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
excludeFromBudget?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AccountManagementProps {
|
interface AccountManagementProps {
|
||||||
@@ -187,6 +188,9 @@ export default function AccountManagement({ accounts, onAccountAdd, onAccountUpd
|
|||||||
<p className={`text-sm ${account.isActive ? 'text-green-600' : 'text-gray-500'}`}>
|
<p className={`text-sm ${account.isActive ? 'text-green-600' : 'text-gray-500'}`}>
|
||||||
{account.isActive ? 'Active' : 'Inactive'}
|
{account.isActive ? 'Active' : 'Inactive'}
|
||||||
</p>
|
</p>
|
||||||
|
{account.excludeFromBudget && (
|
||||||
|
<span className="text-xs bg-gray-100 text-gray-500 px-1.5 py-0.5 rounded">excluded from budget</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
@@ -316,6 +320,20 @@ export default function AccountManagement({ accounts, onAccountAdd, onAccountUpd
|
|||||||
<label htmlFor="isActive" className="text-sm text-gray-700">Active Account</label>
|
<label htmlFor="isActive" className="text-sm text-gray-700">Active Account</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="excludeFromBudget"
|
||||||
|
checked={formData.excludeFromBudget ?? false}
|
||||||
|
onChange={(e) => setFormData(prev => ({ ...prev, excludeFromBudget: e.target.checked }))}
|
||||||
|
className="mr-2 mt-0.5"
|
||||||
|
/>
|
||||||
|
<label htmlFor="excludeFromBudget" className="text-sm text-gray-700">
|
||||||
|
Exclude from budget tracking
|
||||||
|
<span className="block text-xs text-gray-400">Transactions on this account won't count against envelopes or expenses (e.g. mortgage payments)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3 pt-4">
|
<div className="flex justify-end space-x-3 pt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in new issue
Block a user