diff --git a/packages/desktop-client/e2e/rules.test.js b/packages/desktop-client/e2e/rules.test.js index 98aa77a06292e66d09abbe13e1b4ed59858ec5d8..45e3ef2eb9bb9f64aa7350ed0da19a4650212121 100644 --- a/packages/desktop-client/e2e/rules.test.js +++ b/packages/desktop-client/e2e/rules.test.js @@ -120,7 +120,7 @@ test.describe('Rules', () => { }); const transaction = accountPage.getNthTransaction(0); - await expect(transaction.payee).toHaveText('Split'); + await expect(transaction.payee).toHaveText('Ikea'); await expect(transaction.notes).toHaveText('food / entertainment'); await expect(transaction.category).toHaveText('Split'); await expect(transaction.debit).toHaveText('100.00'); diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png index 07d834a864e5b7ed6af26ad6ad2a46951c98a197..0e7fdf49c017a665d12dfb88e54b70678c454e25 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png index 6d301b5e470841d7075c8b89d79da68113b9eccc..0cf0cf7e9763d1708c3a62e91aa5f5c38a5a0b6c 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png index b6bd1b7d2876b6fbf07c0babec89d5fb4de32021..a079162647cc73171bc71f22bb76716b4595b477 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-split-transaction-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js b/packages/desktop-client/e2e/transactions.test.js index 91bdd02c485d73a7f866adc05f99a3cbb5961c47..f0ea08d796feba991c5dad6b0298cbbae59efc1d 100644 --- a/packages/desktop-client/e2e/transactions.test.js +++ b/packages/desktop-client/e2e/transactions.test.js @@ -120,7 +120,7 @@ test.describe('Transactions', () => { ]); const firstTransaction = accountPage.getNthTransaction(0); - await expect(firstTransaction.payee).toHaveText('Split'); + await expect(firstTransaction.payee).toHaveText('Krogger'); await expect(firstTransaction.notes).toHaveText('Notes'); await expect(firstTransaction.category).toHaveText('Split'); await expect(firstTransaction.debit).toHaveText('333.33'); diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png index 987db8a0c82dcbca515b9fd419fb4acb1527efc4..4a23da31b77ef0a417331a84b50bc8d40c319da9 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png index 123571a606f7d16b1a53e93199d62e5daefd168a..b1a0d8f7bbc1f49ac4409f94f28ca5d03a006e3c 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png index ae65b4cb2386051c967c5bc50c4d4892200f7d94..9221ba5b04b1003d868d5b20987ab78e682c310a 100644 Binary files a/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png and b/packages/desktop-client/e2e/transactions.test.js-snapshots/Transactions-creates-a-split-test-transaction-3-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 5de3b0abc1ef18c4539e3ead6be5e431ddd249a2..4fd482c82b56713e4025c0df0963548d0f0b87bf 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -317,7 +317,10 @@ const TransactionHeader = memo( TransactionHeader.displayName = 'TransactionHeader'; -function getPayeePretty(transaction, payee, transferAcct) { +function getPayeePretty(transaction, payee, transferAcct, numHiddenPayees = 0) { + const formatPayeeName = payeeName => + numHiddenPayees > 0 ? `${payeeName} (+${numHiddenPayees} more)` : payeeName; + const { payee: payeeId } = transaction; if (transferAcct) { @@ -334,14 +337,14 @@ function getPayeePretty(transaction, payee, transferAcct) { textOverflow: 'ellipsis', }} > - {transferAcct.name} + {formatPayeeName(transferAcct.name)} </div> </View> ); } else if (payee) { - return payee.name; + return formatPayeeName(payee.name); } else if (payeeId && payeeId.startsWith('new:')) { - return payeeId.slice('new:'.length); + return formatPayeeName(payeeId.slice('new:'.length)); } return ''; @@ -472,15 +475,59 @@ function HeaderCell({ ); } +const useParentPayee = ( + payees, + subtransactions, + transferAccountsByTransaction, +) => + useMemo(() => { + if (!subtransactions) { + return null; + } + + const { counts, mostCommonPayeeTransaction } = + subtransactions?.reduce( + ({ counts, ...result }, sub) => { + if (sub.payee) { + counts[sub.payee] = (counts[sub.payee] || 0) + 1; + if (counts[sub.payee] > result.maxCount) { + return { + counts, + maxCount: counts[sub.payee], + mostCommonPayeeTransaction: sub, + }; + } + } + return { counts, ...result }; + }, + { counts: {}, maxCount: 0, mostCommonPayeeTransaction: null }, + ) || {}; + + if (!mostCommonPayeeTransaction) { + return 'Split (no payee)'; + } + + const mostCommonPayee = + getPayeesById(payees)[mostCommonPayeeTransaction.payee]; + const numDistinctPayees = Object.keys(counts).length; + return getPayeePretty( + mostCommonPayeeTransaction, + mostCommonPayee, + transferAccountsByTransaction[mostCommonPayeeTransaction.id], + numDistinctPayees - 1, + ); + }, [subtransactions, payees, transferAccountsByTransaction]); + function PayeeCell({ id, payee, focused, payees, accounts, + transferAccountsByTransaction, valueStyle, transaction, - transferAcct, + subtransactions, isPreview, onEdit, onUpdate, @@ -493,6 +540,14 @@ function PayeeCell({ const dispatch = useDispatch(); + const parentPayee = useParentPayee( + payees, + subtransactions, + transferAccountsByTransaction, + ); + + const transferAccount = transferAccountsByTransaction[transaction.id]; + return transaction.is_parent ? ( <Cell name="payee" @@ -538,7 +593,7 @@ function PayeeCell({ color: 'inherit', width: 14, height: 14, - marginRight: 2, + marginRight: 5, }} /> <Text @@ -548,7 +603,7 @@ function PayeeCell({ userSelect: 'none', }} > - Split + {parentPayee} </Text> </View> </CellButton> @@ -572,12 +627,12 @@ function PayeeCell({ isCreatingPayee.current = false; } }} - formatter={() => getPayeePretty(transaction, payee, transferAcct)} + formatter={() => getPayeePretty(transaction, payee, transferAccount)} unexposedContent={props => ( <> <PayeeIcons transaction={transaction} - transferAccount={transferAcct} + transferAccount={transferAccount} onNavigateToTransferAccount={onNavigateToTransferAccount} onNavigateToSchedule={onNavigateToSchedule} /> @@ -695,6 +750,7 @@ const Transaction = memo(function Transaction({ allTransactions, transaction: originalTransaction, subtransactions, + transferAccountsByTransaction, editing, showAccount, showBalance, @@ -872,19 +928,9 @@ const Transaction = memo(function Transaction({ // Join in some data const payee = payees && payeeId && getPayeesById(payees)[payeeId]; const account = accounts && accountId && getAccountsById(accounts)[accountId]; - let transferAcct; - - if (_inverse) { - transferAcct = - accounts && accountId && getAccountsById(accounts)[accountId]; - } else { - transferAcct = - payee && - payee.transfer_acct && - getAccountsById(accounts)[payee.transfer_acct]; - } const isChild = transaction.is_child; + const transferAcct = transferAccountsByTransaction[id]; const isBudgetTransfer = transferAcct && transferAcct.offbudget === 0; const isOffBudget = account && account.offbudget === 1; @@ -1116,7 +1162,8 @@ const Transaction = memo(function Transaction({ payees={payees.filter(payee => payee.transfer_acct !== accountId)} valueStyle={valueStyle} transaction={transaction} - transferAcct={transferAcct} + subtransactions={subtransactions} + transferAccountsByTransaction={transferAccountsByTransaction} importedPayee={importedPayee} isPreview={isPreview} onEdit={onEdit} @@ -1510,6 +1557,7 @@ function NewTransaction({ accounts, categoryGroups, payees, + transferAccountsByTransaction, editingTransaction, focusedField, showAccount, @@ -1562,6 +1610,7 @@ function NewTransaction({ editing={editingTransaction === transaction.id} transaction={transaction} subtransactions={transaction.is_parent ? childTransactions : null} + transferAccountsByTransaction={transferAccountsByTransaction} showAccount={showAccount} showCategory={showCategory} showBalance={showBalance} @@ -1716,17 +1765,20 @@ function TransactionTableInner({ error && error.type === 'SplitTransactionError'; - const emptyChildTransactions = transactions.filter( - t => - t.parent_id === (trans.is_parent ? trans.id : trans.parent_id) && - t.amount === 0, - ); + const childTransactions = trans.is_parent + ? props.transactionsByParent[trans.id] + : null; + const emptyChildTransactions = props.transactionsByParent[ + trans.is_parent ? trans.id : trans.parent_id + ]?.filter(t => t.amount === 0); return ( <Transaction allTransactions={props.transactions} editing={editing} transaction={trans} + transferAccountsByTransaction={props.transferAccountsByTransaction} + subtransactions={childTransactions} showAccount={showAccount} showCategory={showCategory} showBalance={showBalances} @@ -1802,6 +1854,9 @@ function TransactionTableInner({ > <NewTransaction transactions={props.newTransactions} + transferAccountsByTransaction={ + props.transferAccountsByTransaction + } editingTransaction={newNavigator.editingId} focusedField={newNavigator.focusedField} accounts={props.accounts} @@ -1885,7 +1940,7 @@ export const TransactionTable = forwardRef((props, ref) => { const listContainerRef = useRef(null); const mergedRef = useMergedRefs(tableRef, ref); - const transactions = useMemo(() => { + const transactionsWithExpandedSplits = useMemo(() => { let result; if (splitsExpanded.state.transitionId != null) { const index = props.transactions.findIndex( @@ -1923,8 +1978,44 @@ export const TransactionTable = forwardRef((props, ref) => { return result; }, [props.transactions, splitsExpanded]); const transactionMap = useMemo(() => { - return new Map(transactions.map(trans => [trans.id, trans])); - }, [transactions]); + return new Map( + transactionsWithExpandedSplits.map(trans => [trans.id, trans]), + ); + }, [transactionsWithExpandedSplits]); + const transactionsByParent = useMemo(() => { + return props.transactions.reduce((acc, trans) => { + if (trans.is_child) { + acc[trans.parent_id] = [...(acc[trans.parent_id] ?? []), trans]; + } + return acc; + }, {}); + }, [props.transactions]); + + const transferAccountsByTransaction = useMemo(() => { + if (!props.accounts) { + return {}; + } + const accounts = getAccountsById(props.accounts); + const payees = getPayeesById(props.payees); + + return Object.fromEntries( + props.transactions.map(t => { + if (!props.accounts) { + return [t.id, null]; + } + + const payee = t.payee && payees[t.payee]; + let transferAccount; + if (t._inverse) { + transferAccount = t.account && accounts[t.account]; + } else { + transferAccount = + payee?.transfer_acct && accounts[payee.transfer_acct]; + } + return [t.id, transferAccount || null]; + }), + ); + }, [props.transactions, props.payees, props.accounts]); useEffect(() => { // If it's anchored that means we've also disabled animations. To @@ -1937,7 +2028,10 @@ export const TransactionTable = forwardRef((props, ref) => { }, [prevSplitsExpanded.current]); const newNavigator = useTableNavigator(newTransactions, getFields); - const tableNavigator = useTableNavigator(transactions, getFields); + const tableNavigator = useTableNavigator( + transactionsWithExpandedSplits, + getFields, + ); const shouldAdd = useRef(false); const latestState = useRef({ newTransactions, newNavigator, tableNavigator }); const savePending = useRef(false); @@ -2297,8 +2391,10 @@ export const TransactionTable = forwardRef((props, ref) => { tableRef={mergedRef} listContainerRef={listContainerRef} {...props} - transactions={transactions} + transactions={transactionsWithExpandedSplits} transactionMap={transactionMap} + transactionsByParent={transactionsByParent} + transferAccountsByTransaction={transferAccountsByTransaction} selectedItems={selectedItems} isExpanded={splitsExpanded.expanded} onSave={onSave} diff --git a/upcoming-release-notes/3049.md b/upcoming-release-notes/3049.md new file mode 100644 index 0000000000000000000000000000000000000000..b8429a26cde16aa4d843bdf2aa8261c67696f0c6 --- /dev/null +++ b/upcoming-release-notes/3049.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [jfdoming] +--- + +Include more information in payee of split parent