From 7767ca730d8d4b4f880e3f95bf48283d909cca36 Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Sun, 8 Sep 2024 20:56:20 -0700 Subject: [PATCH] Fix #3230 --- .../src/components/accounts/Account.tsx | 33 +++++++------ .../mobile/accounts/AccountTransactions.tsx | 4 +- .../mobile/transactions/Transaction.jsx | 20 ++++---- .../mobile/transactions/TransactionEdit.jsx | 49 +++++++------------ .../src/components/mobile/usePrettyPayee.tsx | 42 ++++++++++++++++ .../transactions/TransactionsTable.jsx | 34 ++++++------- .../src/hooks/usePreviewTransactions.ts | 29 +++++++---- packages/loot-core/src/shared/schedules.ts | 13 +++-- .../loot-core/src/types/models/schedule.d.ts | 2 +- 9 files changed, 138 insertions(+), 88 deletions(-) create mode 100644 packages/desktop-client/src/components/mobile/usePrettyPayee.tsx diff --git a/packages/desktop-client/src/components/accounts/Account.tsx b/packages/desktop-client/src/components/accounts/Account.tsx index 05e30623b7..e11aefdf30 100644 --- a/packages/desktop-client/src/components/accounts/Account.tsx +++ b/packages/desktop-client/src/components/accounts/Account.tsx @@ -5,6 +5,7 @@ import React, { createRef, useMemo, type ReactElement, + useEffect, } from 'react'; import { Trans } from 'react-i18next'; import { useSelector } from 'react-redux'; @@ -53,7 +54,10 @@ import { useDateFormat } from '../../hooks/useDateFormat'; import { useFailedAccounts } from '../../hooks/useFailedAccounts'; import { useLocalPref } from '../../hooks/useLocalPref'; import { usePayees } from '../../hooks/usePayees'; -import { usePreviewTransactions } from '../../hooks/usePreviewTransactions'; +import { + type PreviewTransactionEntity, + usePreviewTransactions, +} from '../../hooks/usePreviewTransactions'; import { SelectedProviderWithItems, type Actions, @@ -141,7 +145,6 @@ type AllTransactionsProps = { transactions: TransactionEntity[], balances: Record | null, ) => ReactElement; - collapseTransactions: (ids: string[]) => void; }; function AllTransactions({ @@ -151,14 +154,18 @@ function AllTransactions({ showBalances, filtered, children, - collapseTransactions, }: AllTransactionsProps) { const accountId = account?.id; - const prependTransactions: (TransactionEntity & { _inverse?: boolean })[] = - usePreviewTransactions(collapseTransactions).map(trans => ({ - ...trans, - _inverse: accountId ? accountId !== trans.account : false, - })); + const { dispatch: splitsExpandedDispatch } = useSplitsExpanded(); + const prependTransactions: PreviewTransactionEntity[] = + usePreviewTransactions({ accountId }); + + useEffect(() => { + splitsExpandedDispatch({ + type: 'close-splits', + ids: prependTransactions.filter(t => t.is_parent).map(t => t.id), + }); + }, [prependTransactions, splitsExpandedDispatch]); transactions ??= []; @@ -181,9 +188,10 @@ function AllTransactions({ const scheduledBalances = [...prependTransactions] .reverse() .map(scheduledTransaction => { - const amount = - (scheduledTransaction._inverse ? -1 : 1) * - getScheduledAmount(scheduledTransaction.amount); + const amount = getScheduledAmount( + scheduledTransaction.amount, + scheduledTransaction._inverse, + ); return { // TODO: fix me // eslint-disable-next-line react-hooks/exhaustive-deps @@ -1657,9 +1665,6 @@ class AccountInternal extends PureComponent< balances={balances} showBalances={showBalances} filtered={transactionsFiltered} - collapseTransactions={ids => - this.props.splitsExpandedDispatch({ type: 'close-splits', ids }) - } > {(allTransactions, allBalances) => ( >([]); - const prependTransactions = usePreviewTransactions(); + const prependTransactions = usePreviewTransactions({ + accountId: account?.id, + }); const allTransactions = useMemo( () => !isSearching ? prependTransactions.concat(transactions) : transactions, diff --git a/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx b/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx index 25582ed854..e2b2dfa2d7 100644 --- a/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx @@ -26,8 +26,9 @@ import { Button } from '../../common/Button2'; import { Text } from '../../common/Text'; import { TextOneLine } from '../../common/TextOneLine'; import { View } from '../../common/View'; +import { usePrettyPayee } from '../usePrettyPayee'; -import { lookupName, getDescriptionPretty, Status } from './TransactionEdit'; +import { lookupName, Status } from './TransactionEdit'; const ROW_HEIGHT = 50; @@ -71,11 +72,12 @@ export const Transaction = memo(function Transaction({ is_parent: isParent, is_child: isChild, schedule, + _inverse, } = transaction; const payee = usePayee(payeeId); const account = useAccount(accountId); - const transferAcct = useAccount(payee?.transfer_acct); + const transferAccount = useAccount(payee?.transfer_acct); const isPreview = isPreviewId(id); const { longPressProps } = useLongPress({ @@ -97,19 +99,19 @@ export const Transaction = memo(function Transaction({ let amount = originalAmount; if (isPreview) { - amount = getScheduledAmount(amount); + amount = getScheduledAmount(amount, _inverse); } const categoryName = lookupName(categories, categoryId); - const prettyDescription = getDescriptionPretty( + const prettyPayee = usePrettyPayee({ transaction, payee, - transferAcct, - ); + transferAccount, + }); const specialCategory = account?.offbudget ? 'Off Budget' - : transferAcct && !transferAcct.offbudget + : transferAccount && !transferAccount.offbudget ? 'Transfer' : isParent ? 'Split' @@ -171,13 +173,13 @@ export const Transaction = memo(function Transaction({ ...textStyle, fontSize: 14, fontWeight: isAdded ? '600' : '400', - ...(prettyDescription === '' && { + ...(prettyPayee === '' && { color: theme.tableTextLight, fontStyle: 'italic', }), }} > - {prettyDescription || 'Empty'} + {prettyPayee || 'Empty'} {isPreview ? ( diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx index b21dfbd3b4..cb4142b71b 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx @@ -61,6 +61,7 @@ import { MobilePageHeader, Page } from '../../Page'; import { AmountInput } from '../../util/AmountInput'; import { MobileBackButton } from '../MobileBackButton'; import { FieldLabel, TapField, InputField, BooleanField } from '../MobileForms'; +import { usePrettyPayee } from '../usePrettyPayee'; import { FocusableAmountInput } from './FocusableAmountInput'; @@ -68,18 +69,6 @@ function getFieldName(transactionId, field) { return `${field}-${transactionId}`; } -export function getDescriptionPretty(transaction, payee, transferAcct) { - const { amount } = transaction; - - if (transferAcct) { - return `Transfer ${amount > 0 ? 'from' : 'to'} ${transferAcct.name}`; - } else if (payee) { - return payee.name; - } - - return ''; -} - function serializeTransaction(transaction, dateFormat) { const { date, amount } = transaction; return { @@ -288,7 +277,8 @@ const ChildTransactionEdit = forwardRef( amountFocused, amountSign, getCategory, - getPrettyPayee, + getPayee, + getTransferAccount, isOffBudget, isBudgetTransfer, onEditField, @@ -299,6 +289,11 @@ const ChildTransactionEdit = forwardRef( ) => { const { editingField, onRequestActiveEdit, onClearActiveEdit } = useSingleActiveEditForm(); + const prettyPayee = usePrettyPayee({ + transaction, + payee: getPayee(transaction), + transferAccount: getTransferAccount(transaction), + }); return ( onEditField(transaction.id, 'payee')} data-testid={`payee-field-${transaction.id}`} /> @@ -488,22 +483,13 @@ const TransactionEditInner = memo(function TransactionEditInner({ return trans?.payee && payeesById?.[trans.payee]; }; - const getTransferAcct = trans => { + const getTransferAccount = trans => { const payee = trans && getPayee(trans); return payee?.transfer_acct && accountsById?.[payee.transfer_acct]; }; - const getPrettyPayee = trans => { - if (trans && trans.is_parent) { - return 'Split'; - } - const transPayee = trans && getPayee(trans); - const transTransferAcct = trans && getTransferAcct(trans); - return getDescriptionPretty(trans, transPayee, transTransferAcct); - }; - const isBudgetTransfer = trans => { - const transferAcct = trans && getTransferAcct(trans); + const transferAcct = trans && getTransferAccount(trans); return transferAcct && !transferAcct.offbudget; }; @@ -700,11 +686,11 @@ const TransactionEditInner = memo(function TransactionEditInner({ const account = getAccount(transaction); const isOffBudget = account && !!account.offbudget; - const title = getDescriptionPretty( + const title = usePrettyPayee({ transaction, - getPayee(transaction), - getTransferAcct(transaction), - ); + payee: getPayee(transaction), + transferAccount: getTransferAccount(transaction), + }); const transactionDate = parseDate(transaction.date, dateFormat, new Date()); const dateDefaultValue = monthUtils.dayFromDate(transactionDate); @@ -775,7 +761,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ fontWeight: 300, }), }} - value={getPrettyPayee(transaction)} + value={title} disabled={ editingField && editingField !== getFieldName(transaction.id, 'payee') @@ -823,7 +809,8 @@ const TransactionEditInner = memo(function TransactionEditInner({ }} isOffBudget={isOffBudget} getCategory={getCategory} - getPrettyPayee={getPrettyPayee} + getPayee={getPayee} + getTransferAccount={getTransferAccount} isBudgetTransfer={isBudgetTransfer} onUpdate={onUpdate} onEditField={onEditField} diff --git a/packages/desktop-client/src/components/mobile/usePrettyPayee.tsx b/packages/desktop-client/src/components/mobile/usePrettyPayee.tsx new file mode 100644 index 0000000000..f68179d5e9 --- /dev/null +++ b/packages/desktop-client/src/components/mobile/usePrettyPayee.tsx @@ -0,0 +1,42 @@ +import { isPreviewId } from 'loot-core/shared/transactions'; +import { useAccount } from '../../hooks/useAccount'; +import { + AccountEntity, + PayeeEntity, + TransactionEntity, +} from 'loot-core/types/models'; + +type UsePrettyPayeeProps = { + transaction: TransactionEntity & { _inverse?: boolean }; + payee?: PayeeEntity; + transferAccount?: AccountEntity; +}; + +export function usePrettyPayee({ + transaction, + payee, + transferAccount, +}: UsePrettyPayeeProps) { + const { id, amount: originalAmount, account, _inverse } = transaction; + const transactionAccount = useAccount(account); + + const isPreview = isPreviewId(id); + const amount = isPreview + ? (_inverse ? -1 : 1) * originalAmount + : originalAmount; + + if (transferAccount) { + const transferAccountName = isPreview + ? _inverse + ? transactionAccount?.name + : transferAccount.name + : transferAccount.name; + return `Transfer ${amount > 0 ? 'from' : 'to'} ${transferAccountName}`; + } else if (transaction.is_parent) { + return 'Split'; + } else if (payee) { + return payee.name; + } + + return ''; +} diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 36423d2b52..6bd049a8ea 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -92,7 +92,7 @@ function serializeTransaction(transaction, showZeroInDeposit) { let { amount, date } = transaction; if (isPreviewId(transaction.id)) { - amount = (transaction._inverse ? -1 : 1) * getScheduledAmount(amount); + amount = getScheduledAmount(amount, transaction._inverse); } let debit = amount < 0 ? -amount : null; @@ -744,6 +744,16 @@ function PayeeCell({ ); } +const payeeIconButtonStyle = { + marginLeft: -5, + marginRight: 2, + width: 23, + height: 23, + color: 'inherit', +}; +const scheduleIconStyle = { width: 13, height: 13 }; +const transferIconStyle = { width: 10, height: 10 }; + function PayeeIcons({ transaction, transferAccount, @@ -757,27 +767,13 @@ function PayeeIcons({ ? scheduleData.schedules.find(s => s.id === scheduleId) : null; - const buttonStyle = useMemo( - () => ({ - marginLeft: -5, - marginRight: 2, - width: 23, - height: 23, - color: 'inherit', - }), - [], - ); - - const scheduleIconStyle = useMemo(() => ({ width: 13, height: 13 }), []); - - const transferIconStyle = useMemo(() => ({ width: 10, height: 10 }), []); - if (schedule == null && transferAccount == null) { // Neither a valid scheduled transaction nor a transfer. return null; } const recurring = schedule && schedule._date && !!schedule._date.frequency; + const isDeposit = (transaction._inverse ? -1 : 1) * transaction.amount > 0; return ( <> @@ -785,7 +781,7 @@ function PayeeIcons({