diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 77db1627272bf67fbab05318715a744da448cee4..bca1906fcea465abb3791d7453277c973181e0ae 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -1349,7 +1349,7 @@ const Transaction = memo(function Transaction(props) { ); }); -function TransactionError({ error, isDeposit, onAddSplit, style }) { +function TransactionError({ error, isDeposit, onAddSplit, onDistributeRemainder, style, canDistributeRemainder }) { switch (error.type) { case 'SplitTransactionError': if (error.version === 1) { @@ -1372,10 +1372,20 @@ function TransactionError({ error, isDeposit, onAddSplit, style }) { </Text> </Text> <View style={{ flex: 1 }} /> + <Button + type="normal" + style={{ marginLeft: 15 }} + onClick={onDistributeRemainder} + data-testid="distribute-split-button" + disabled={!canDistributeRemainder} + > + Distribute + </Button> <Button type="primary" - style={{ marginLeft: 15, padding: '4px 10px' }} + style={{ marginLeft: 10, padding: '4px 10px' }} onClick={onAddSplit} + data-testid="add-split-button" > Add Split </Button> @@ -1433,6 +1443,7 @@ function NewTransaction({ onSave, onAdd, onAddSplit, + onDistributeRemainder, onManagePayees, onCreatePayee, onNavigateToTransferAccount, @@ -1442,6 +1453,8 @@ function NewTransaction({ const error = transactions[0].error; const isDeposit = transactions[0].amount > 0; + const emptyChildTransactions = transactions.filter(t => t.parent_id === transactions[0].id && t.amount === 0) + return ( <View style={{ @@ -1508,6 +1521,8 @@ function NewTransaction({ error={error} isDeposit={isDeposit} onAddSplit={() => onAddSplit(transactions[0].id)} + onDistributeRemainder={() => onDistributeRemainder(transactions[0].id)} + canDistributeRemainder={emptyChildTransactions.length > 0} /> ) : ( <Button @@ -1597,14 +1612,21 @@ function TransactionTableInner({ ? (parent && parent.error) || trans.error : trans.error; + const hasSplitError = + (!expanded || isLastChild(transactions, index)) && + error && + error.type === 'SplitTransactionError' + + const emptyChildTransactions = transactions.filter( + t => t.parent_id === (trans.is_parent ? trans.id : trans.parent_id) && t.amount === 0 + ) + return ( <> - {(!expanded || isLastChild(transactions, index)) && - error && - error.type === 'SplitTransactionError' && ( + {hasSplitError && ( <Tooltip position="bottom-right" - width={250} + width={350} forceTop={position} forceLayout={true} style={{ transform: 'translate(-5px, 2px)' }} @@ -1613,6 +1635,8 @@ function TransactionTableInner({ error={error} isDeposit={isChildDeposit} onAddSplit={() => props.onAddSplit(trans.id)} + onDistributeRemainder={() => props.onDistributeRemainder(trans.id)} + canDistributeRemainder={emptyChildTransactions.length > 0} /> </Tooltip> )} @@ -1706,6 +1730,7 @@ function TransactionTableInner({ onCreatePayee={props.onCreatePayee} onNavigateToTransferAccount={onNavigateToTransferAccount} onNavigateToSchedule={onNavigateToSchedule} + onDistributeRemainder={props.onDistributeRemainder} balance={ props.transactions?.length > 0 ? props.balances?.[props.transactions[0]?.id]?.balance @@ -2083,6 +2108,62 @@ export const TransactionTable = forwardRef((props, ref) => { [props.onAddSplit], ); + const onDistributeRemainder = useCallback( + async id => { + const { transactions, tableNavigator, newTransactions } = + latestState.current; + + const targetTransactions = isTemporaryId(id) ? newTransactions : transactions + const transaction = targetTransactions.find(t => t.id === id); + + const parentTransaction = transaction.is_parent ? transaction : targetTransactions.find( + t => t.id === transaction.parent_id + ) + + const siblingTransactions = targetTransactions.filter( + t => t.parent_id === (transaction.is_parent ? transaction.id : transaction.parent_id) + ) + + const emptyTransactions = siblingTransactions.filter(t => t.amount === 0); + + const remainingAmount = + parentTransaction.amount - + siblingTransactions.reduce((acc, t) => acc + t.amount, 0); + + const amountPerTransaction = Math.floor( + remainingAmount / emptyTransactions.length, + ); + let remainingCents = + remainingAmount - amountPerTransaction * emptyTransactions.length; + + let amounts = new Array(emptyTransactions.length).fill( + amountPerTransaction, + ); + + for (let amountIndex in amounts) { + if (remainingCents === 0) break; + + amounts[amountIndex] += 1; + remainingCents--; + } + + if (isTemporaryId(id)) { + newNavigator.onEdit(null) + } else { + tableNavigator.onEdit(null) + } + + for (const transactionIndex in emptyTransactions) { + await onSave({ + ...emptyTransactions[transactionIndex], + amount: amounts[transactionIndex], + }); + } + }, + [latestState], + ); + + function onCloseAddTransaction() { setNewTransactions( makeTemporaryTransactions( @@ -2113,6 +2194,7 @@ export const TransactionTable = forwardRef((props, ref) => { onCheckEnter={onCheckEnter} onAddTemporary={onAddTemporary} onAddSplit={onAddSplit} + onDistributeRemainder={onDistributeRemainder} onCloseAddTransaction={onCloseAddTransaction} onToggleSplit={onToggleSplit} newTransactions={newTransactions} diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx index 4a925a8e92b33449b6ae3869f94e4436bc4deabe..bdb29b763c2343c88d3bd40de2f9f2089ccb4e62 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx @@ -668,7 +668,7 @@ describe('Transactions', () => { await waitForAutocomplete(); await userEvent.click( - container.querySelector('[data-testid="transaction-error"] button'), + container.querySelector('[data-testid="add-split-button"]'), ); input = await editNewField(container, 'debit', 1); @@ -794,6 +794,7 @@ describe('Transactions', () => { expect(getTransactions().length).toBe(5); await userEvent.click(splitButton); await waitForAutocomplete(); + expect(getTransactions().length).toBe(6); expect(getTransactions()[0].is_parent).toBe(true); expect(getTransactions()[1].is_child).toBe(true); @@ -816,7 +817,7 @@ describe('Transactions', () => { // Add another split transaction and make sure everything is // updated properly - await userEvent.click(toolbar.querySelector('button')); + await userEvent.click(toolbar.querySelector('[data-testid="add-split-button"]')); expect(getTransactions().length).toBe(7); expect(getTransactions()[2].amount).toBe(0); expectErrorToExist(getTransactions().slice(0, 3)); @@ -910,7 +911,7 @@ describe('Transactions', () => { await userEvent.click(splitButton); await waitForAutocomplete(); await userEvent.click( - container.querySelector('[data-testid="transaction-error"] button'), + container.querySelector('[data-testid="add-split-button"]'), ); expect(getTransactions().length).toBe(7); diff --git a/upcoming-release-notes/2151.md b/upcoming-release-notes/2151.md new file mode 100644 index 0000000000000000000000000000000000000000..d8f80fd73825a47109632b31f117400cd82d6c72 --- /dev/null +++ b/upcoming-release-notes/2151.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [NikxDa] +--- + +Add "Distribute" button to distribute remaining split amount across empty splits.