diff --git a/packages/desktop-client/src/components/accounts/Account.js b/packages/desktop-client/src/components/accounts/Account.js index e20366d8da2..c2f2de4cabc 100644 --- a/packages/desktop-client/src/components/accounts/Account.js +++ b/packages/desktop-client/src/components/accounts/Account.js @@ -74,10 +74,19 @@ function EmptyMessage({ onAdd }) { ); } -function AllTransactions({ account = {}, transactions, filtered, children }) { +function AllTransactions({ + account = {}, + transactions, + balances, + showBalances, + filtered, + children, +}) { const { id: accountId } = account; let scheduleData = useCachedSchedules(); + transactions ??= []; + let schedules = useMemo( () => scheduleData @@ -94,7 +103,7 @@ function AllTransactions({ account = {}, transactions, filtered, children }) { let prependTransactions = useMemo(() => { return schedules.map(schedule => ({ - id: 'preview/' + schedule.id, + id: `preview/${schedule.id}`, payee: schedule._payee, account: schedule._account, amount: schedule._amount, @@ -105,6 +114,36 @@ function AllTransactions({ account = {}, transactions, filtered, children }) { })); }, [schedules, accountId]); + let runningBalance = useMemo(() => { + if (!showBalances) { + return 0; + } + + return balances && transactions?.length > 0 + ? balances[transactions[0].id]?.balance ?? 0 + : 0; + }, [showBalances, balances, transactions]); + + let prependBalances = useMemo(() => { + if (!showBalances) { + return null; + } + + // Reverse so we can calculate from earliest upcoming schedule. + let scheduledBalances = [...prependTransactions] + .reverse() + .map(scheduledTransaction => { + let amount = + (scheduledTransaction._inverse ? -1 : 1) * + scheduledTransaction.amount; + return { + balance: (runningBalance += amount), + id: scheduledTransaction.id, + }; + }); + return groupById(scheduledBalances); + }, [showBalances, prependTransactions, runningBalance]); + let allTransactions = useMemo(() => { // Don't prepend scheduled transactions if we are filtering if (!filtered && prependTransactions.length > 0) { @@ -113,10 +152,18 @@ function AllTransactions({ account = {}, transactions, filtered, children }) { return transactions; }, [filtered, prependTransactions, transactions]); + let allBalances = useMemo(() => { + // Don't prepend scheduled transactions if we are filtering + if (!filtered && prependBalances && balances) { + return { ...prependBalances, ...balances }; + } + return balances; + }, [filtered, prependBalances, balances]); + if (scheduleData == null) { - return children(null); + return children(transactions, balances); } - return children(allTransactions); + return children(allTransactions, allBalances); } function getField(field) { @@ -152,7 +199,7 @@ class AccountInternal extends PureComponent { transactions: [], transactionsCount: 0, showBalances: props.showBalances, - balances: [], + balances: null, showCleared: props.showCleared, editingName: false, isAdding: false, @@ -340,12 +387,11 @@ class AccountInternal extends PureComponent { transactionsFiltered: isFiltered, loading: false, workingHard: false, + balances: this.state.showBalances + ? await this.calculateBalances() + : null, }, () => { - if (this.state.showBalances) { - this.calculateBalances(); - } - if (firstLoad) { this.table.current?.scrollToTop(); } @@ -372,7 +418,7 @@ class AccountInternal extends PureComponent { loading: true, search: '', showBalances: nextProps.showBalances, - balances: [], + balances: null, showCleared: nextProps.showCleared, }, () => { @@ -484,7 +530,7 @@ class AccountInternal extends PureComponent { async calculateBalances() { if (!this.canCalculateBalance()) { - return; + return null; } let { data } = await runQuery( @@ -494,7 +540,7 @@ class AccountInternal extends PureComponent { .select([{ balance: { $sumOver: '$amount' } }]), ); - this.setState({ balances: groupById(data) }); + return groupById(data); } onAddTransaction = () => { @@ -549,32 +595,36 @@ class AccountInternal extends PureComponent { case 'toggle-balance': if (this.state.showBalances) { this.props.savePrefs({ ['show-balances-' + accountId]: false }); - this.setState({ showBalances: false, balances: [] }); + this.setState({ showBalances: false, balances: null }); } else { - this.setState({ - transactions: [], - transactionCount: 0, - filters: [], - search: '', - sort: [], - showBalances: true, - }); - this.fetchTransactions(); this.props.savePrefs({ ['show-balances-' + accountId]: true }); - this.calculateBalances(); + this.setState( + { + transactions: [], + transactionCount: 0, + filters: [], + search: '', + sort: [], + showBalances: true, + }, + () => { + this.fetchTransactions(); + }, + ); } break; case 'remove-sorting': { - let filters = this.state.filters; - this.setState({ sort: [] }); - if (filters.length > 0) { - this.applyFilters([...filters]); - } else { - this.fetchTransactions(); - } - if (this.state.search !== '') { - this.onSearch(this.state.search); - } + this.setState({ sort: [] }, () => { + let filters = this.state.filters; + if (filters.length > 0) { + this.applyFilters([...filters]); + } else { + this.fetchTransactions(); + } + if (this.state.search !== '') { + this.onSearch(this.state.search); + } + }); break; } case 'toggle-cleared': @@ -981,16 +1031,25 @@ class AccountInternal extends PureComponent { this.currentQuery = this.rootQuery.filter({ [conditionsOpKey]: [...filters, ...customFilters], }); - this.updateQuery(this.currentQuery, true); - this.setState({ filters: conditions }); + + this.setState({ filters: conditions }, () => { + this.updateQuery(this.currentQuery, true); + }); } else { - this.setState({ transactions: [], transactionCount: 0 }); - this.fetchTransactions(); - this.setState({ filters: conditions }); - } + this.setState( + { + transactions: [], + transactionCount: 0, + filters: conditions, + }, + () => { + this.fetchTransactions(); - if (this.state.sort.length !== 0) { - this.applySort(); + if (this.state.sort.length !== 0) { + this.applySort(); + } + }, + ); } }; @@ -1124,143 +1183,134 @@ class AccountInternal extends PureComponent { - {allTransactions => - allTransactions == null ? null : ( - (this.dispatchSelected = dispatch)} - > - - ( + (this.dispatchSelected = dispatch)} + > + + + + + + this.paged && this.paged.fetchNext() + } + accounts={accounts} + category={category} + categoryGroups={categoryGroups} + payees={payees} + balances={allBalances} showBalances={showBalances} - showExtraBalances={showExtraBalances} showCleared={showCleared} - showEmptyMessage={showEmptyMessage} - balanceQuery={balanceQuery} - canCalculateBalance={this.canCalculateBalance} - isSorted={this.state.sort.length !== 0} - reconcileAmount={reconcileAmount} - search={this.state.search} - filters={this.state.filters} - conditionsOp={this.state.conditionsOp} - savePrefs={this.props.savePrefs} - onSearch={this.onSearch} - onShowTransactions={this.onShowTransactions} - onMenuSelect={this.onMenuSelect} - onAddTransaction={this.onAddTransaction} - onToggleExtraBalances={this.onToggleExtraBalances} - onSaveName={this.onSaveName} - onExposeName={this.onExposeName} - onReconcile={this.onReconcile} - onDoneReconciling={this.onDoneReconciling} - onCreateReconciliationTransaction={ - this.onCreateReconciliationTransaction + showAccount={ + !accountId || + accountId === 'offbudget' || + accountId === 'budgeted' || + accountId === 'uncategorized' + } + isAdding={this.state.isAdding} + isNew={this.isNew} + isMatched={this.isMatched} + isFiltered={ + this.state.search !== '' || this.state.filters.length > 0 } - onSync={this.onSync} - onImport={this.onImport} - onBatchDelete={this.onBatchDelete} - onBatchDuplicate={this.onBatchDuplicate} - onBatchEdit={this.onBatchEdit} - onBatchUnlink={this.onBatchUnlink} - onCreateRule={this.onCreateRule} - onUpdateFilter={this.onUpdateFilter} - onClearFilters={this.onClearFilters} - onReloadSavedFilter={this.onReloadSavedFilter} - onCondOpChange={this.onCondOpChange} - onDeleteFilter={this.onDeleteFilter} - onApplyFilter={this.onApplyFilter} - onScheduleAction={this.onScheduleAction} + dateFormat={dateFormat} + hideFraction={hideFraction} + addNotification={addNotification} + renderEmpty={() => + showEmptyMessage ? ( + replaceModal('add-account')} /> + ) : !loading ? ( + + No transactions + + ) : null + } + onChange={this.onTransactionsChange} + onRefetch={this.refetchTransactions} + onRefetchUpToRow={row => + this.paged.refetchUpToRow(row, { + field: 'date', + order: 'desc', + }) + } + onCloseAddTransaction={() => + this.setState({ isAdding: false }) + } + onCreatePayee={this.onCreatePayee} /> - - - - this.paged && this.paged.fetchNext() - } - accounts={accounts} - category={category} - categoryGroups={categoryGroups} - payees={payees} - balances={ - showBalances && this.canCalculateBalance() - ? balances - : null - } - showCleared={showCleared} - showAccount={ - !accountId || - accountId === 'offbudget' || - accountId === 'budgeted' || - accountId === 'uncategorized' - } - isAdding={this.state.isAdding} - isNew={this.isNew} - isMatched={this.isMatched} - isFiltered={ - this.state.search !== '' || this.state.filters.length > 0 - } - dateFormat={dateFormat} - hideFraction={hideFraction} - addNotification={addNotification} - renderEmpty={() => - showEmptyMessage ? ( - replaceModal('add-account')} - /> - ) : !loading ? ( - - No transactions - - ) : null - } - onSort={this.onSort} - sortField={this.state.sort.field} - ascDesc={this.state.sort.ascDesc} - onChange={this.onTransactionsChange} - onRefetch={this.refetchTransactions} - onRefetchUpToRow={row => - this.paged.refetchUpToRow(row, { - field: 'date', - order: 'desc', - }) - } - onCloseAddTransaction={() => - this.setState({ isAdding: false }) - } - onCreatePayee={this.onCreatePayee} - /> - - - ) - } + + + )} ); } diff --git a/packages/desktop-client/src/components/transactions/TransactionList.js b/packages/desktop-client/src/components/transactions/TransactionList.js index 2e9b20b4968..96c4445b012 100644 --- a/packages/desktop-client/src/components/transactions/TransactionList.js +++ b/packages/desktop-client/src/components/transactions/TransactionList.js @@ -65,6 +65,7 @@ export default function TransactionList({ categoryGroups, payees, balances, + showBalances, showCleared, showAccount, headerContent, @@ -172,6 +173,7 @@ export default function TransactionList({ accounts={accounts} categoryGroups={categoryGroups} payees={payees} + showBalances={showBalances} balances={balances} showCleared={showCleared} showAccount={showAccount} diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.js b/packages/desktop-client/src/components/transactions/TransactionsTable.js index 7df78b74e1b..56786f47803 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.js +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.js @@ -1240,11 +1240,7 @@ const Transaction = memo(function Transaction(props) { {showBalance && ( 0} showAccount={props.showAccount} showCategory={props.showCategory} - showBalance={!!props.balances} + showBalance={props.showBalances} showCleared={props.showCleared} scrollWidth={scrollWidth} onSort={props.onSort} @@ -1619,7 +1614,7 @@ function TransactionTableInner({ payees={props.payees || []} showAccount={props.showAccount} showCategory={props.showCategory} - showBalance={!!props.balances} + showBalance={props.showBalances} showCleared={props.showCleared} dateFormat={dateFormat} hideFraction={props.hideFraction} diff --git a/upcoming-release-notes/1354.md b/upcoming-release-notes/1354.md new file mode 100644 index 00000000000..555096a6932 --- /dev/null +++ b/upcoming-release-notes/1354.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [joel-jeremy] +--- + +Scheduled transactions for the month to show up in Account's running balance