From 897f040170930cc9611506e61931930bfdb048dd Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Sun, 8 Sep 2024 20:56:20 -0700 Subject: [PATCH 01/10] Fix #3230 --- .../src/components/accounts/Account.tsx | 53 ++++++----- .../mobile/accounts/AccountTransactions.tsx | 6 +- .../mobile/transactions/TransactionEdit.jsx | 54 ++++------- .../transactions/TransactionListItem.tsx | 31 +++---- .../src/components/mobile/utils.ts | 31 +++++++ .../transactions/TransactionsTable.jsx | 57 ++++-------- .../hooks/useAccountPreviewTransactions.ts | 92 +++++++++++++++++++ .../src/hooks/usePreviewTransactions.ts | 77 ---------------- .../src/client/data-hooks/transactions.ts | 3 +- packages/loot-core/src/shared/schedules.ts | 13 ++- .../loot-core/src/types/models/schedule.d.ts | 2 +- 11 files changed, 218 insertions(+), 201 deletions(-) create mode 100644 packages/desktop-client/src/components/mobile/utils.ts create mode 100644 packages/desktop-client/src/hooks/useAccountPreviewTransactions.ts delete mode 100644 packages/desktop-client/src/hooks/usePreviewTransactions.ts diff --git a/packages/desktop-client/src/components/accounts/Account.tsx b/packages/desktop-client/src/components/accounts/Account.tsx index 1771ca3168c..77af1f2ed53 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'; @@ -30,7 +31,6 @@ import { import { send, listen } from 'loot-core/src/platform/client/fetch'; import { currentDay } from 'loot-core/src/shared/months'; import { q, type Query } from 'loot-core/src/shared/query'; -import { getScheduledAmount } from 'loot-core/src/shared/schedules'; import { updateTransaction, realizeTempTransactions, @@ -50,6 +50,7 @@ import { type TransactionFilterEntity, } from 'loot-core/src/types/models'; +import { useAccountPreviewTransaction } from '../../hooks/useAccountPreviewTransactions'; import { useAccounts } from '../../hooks/useAccounts'; import { useActions } from '../../hooks/useActions'; import { useCategories } from '../../hooks/useCategories'; @@ -57,7 +58,6 @@ 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 { SelectedProviderWithItems, type Actions, @@ -147,7 +147,6 @@ type AllTransactionsProps = { transactions: TransactionEntity[], balances: Record | null, ) => ReactElement; - collapseTransactions: (ids: string[]) => void; }; function AllTransactions({ @@ -157,14 +156,24 @@ 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 { previewTransactions, isLoading: isPreviewTransactionsLoading } = + useAccountPreviewTransaction({ accountId }); + + useEffect(() => { + if (!isPreviewTransactionsLoading) { + splitsExpandedDispatch({ + type: 'close-splits', + ids: previewTransactions.filter(t => t.is_parent).map(t => t.id), + }); + } + }, [ + isPreviewTransactionsLoading, + previewTransactions, + splitsExpandedDispatch, + ]); transactions ??= []; @@ -184,29 +193,26 @@ function AllTransactions({ } // Reverse so we can calculate from earliest upcoming schedule. - const scheduledBalances = [...prependTransactions] + const previewBalances = [...previewTransactions] .reverse() - .map(scheduledTransaction => { - const amount = - (scheduledTransaction._inverse ? -1 : 1) * - getScheduledAmount(scheduledTransaction.amount); + .map(previewTransaction => { return { // TODO: fix me // eslint-disable-next-line react-hooks/exhaustive-deps - balance: (runningBalance += amount), - id: scheduledTransaction.id, + balance: (runningBalance += previewTransaction.amount), + id: previewTransaction.id, }; }); - return groupById(scheduledBalances); - }, [showBalances, prependTransactions, runningBalance]); + return groupById(previewBalances); + }, [showBalances, previewTransactions, runningBalance]); const allTransactions = useMemo(() => { // Don't prepend scheduled transactions if we are filtering - if (!filtered && prependTransactions.length > 0) { - return prependTransactions.concat(transactions); + if (!filtered && previewTransactions.length > 0) { + return previewTransactions.concat(transactions); } return transactions; - }, [filtered, prependTransactions, transactions]); + }, [filtered, previewTransactions, transactions]); const allBalances = useMemo(() => { // Don't prepend scheduled transactions if we are filtering @@ -216,7 +222,7 @@ function AllTransactions({ return balances; }, [filtered, prependBalances, balances]); - if (!prependTransactions?.length || filtered) { + if (!previewTransactions?.length || filtered) { return children(transactions, balances); } return children(allTransactions, allBalances); @@ -1669,9 +1675,6 @@ class AccountInternal extends PureComponent< balances={balances} showBalances={showBalances} filtered={transactionsFiltered} - collapseTransactions={ids => - this.props.splitsExpandedDispatch({ type: 'close-splits', ids }) - } > {(allTransactions, allBalances) => ( 0 ? 'from' : 'to'} ${transferAcct.name}`; - } else if (payee) { - return payee.name; - } - - return ''; -} - function serializeTransaction(transaction, dateFormat) { const { date, amount } = transaction; return { @@ -291,7 +280,8 @@ const ChildTransactionEdit = forwardRef( amountFocused, amountSign, getCategory, - getPrettyPayee, + getPayee, + getTransferAccount, isOffBudget, isBudgetTransfer, onEditField, @@ -302,6 +292,11 @@ const ChildTransactionEdit = forwardRef( ) => { const { editingField, onRequestActiveEdit, onClearActiveEdit } = useSingleActiveEditForm(); + const prettyPayee = getPrettyPayee({ + transaction, + payee: getPayee(transaction), + transferAccount: getTransferAccount(transaction), + }); return ( onEditField(transaction.id, 'payee')} data-testid={`payee-field-${transaction.id}`} /> @@ -503,7 +498,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ [payeesById], ); - const getTransferAcct = useCallback( + const getTransferAccount = useCallback( trans => { const payee = trans && getPayee(trans); return payee?.transfer_acct && accountsById?.[payee.transfer_acct]; @@ -511,24 +506,12 @@ const TransactionEditInner = memo(function TransactionEditInner({ [accountsById, getPayee], ); - const getPrettyPayee = useCallback( - trans => { - if (trans?.is_parent) { - return 'Split'; - } - const transPayee = trans && getPayee(trans); - const transTransferAcct = trans && getTransferAcct(trans); - return getDescriptionPretty(trans, transPayee, transTransferAcct); - }, - [getPayee, getTransferAcct], - ); - const isBudgetTransfer = useCallback( trans => { - const transferAcct = trans && getTransferAcct(trans); + const transferAcct = trans && getTransferAccount(trans); return transferAcct && !transferAcct.offbudget; }, - [getTransferAcct], + [getTransferAccount], ); const getCategory = useCallback( @@ -759,11 +742,11 @@ const TransactionEditInner = memo(function TransactionEditInner({ const account = getAccount(transaction); const isOffBudget = account && !!account.offbudget; - const title = getDescriptionPretty( + const title = getPrettyPayee({ transaction, - getPayee(transaction), - getTransferAcct(transaction), - ); + payee: getPayee(transaction), + transferAccount: getTransferAccount(transaction), + }); const transactionDate = parseDate(transaction.date, dateFormat, new Date()); const dateDefaultValue = monthUtils.dayFromDate(transactionDate); @@ -834,7 +817,7 @@ const TransactionEditInner = memo(function TransactionEditInner({ fontWeight: 300, }), }} - value={getPrettyPayee(transaction)} + value={title} disabled={ editingField && editingField !== getFieldName(transaction.id, 'payee') @@ -882,7 +865,8 @@ const TransactionEditInner = memo(function TransactionEditInner({ }} isOffBudget={isOffBudget} getCategory={getCategory} - getPrettyPayee={getPrettyPayee} + getPayee={getPayee} + getTransferAccount={getTransferAccount} isBudgetTransfer={isBudgetTransfer} onUpdate={onUpdateInner} onEditField={onEditFieldInner} diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx index a929d96f41e..efc570a52a6 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx @@ -12,7 +12,6 @@ import { useLongPress, } from '@react-aria/interactions'; -import { getScheduledAmount } from 'loot-core/src/shared/schedules'; import { isPreviewId } from 'loot-core/src/shared/transactions'; import { integerToCurrency } from 'loot-core/src/shared/util'; import { type TransactionEntity } from 'loot-core/types/models'; @@ -32,8 +31,9 @@ import { Button } from '../../common/Button2'; import { Text } from '../../common/Text'; import { TextOneLine } from '../../common/TextOneLine'; import { View } from '../../common/View'; +import { getPrettyPayee } from '../utils'; -import { lookupName, getDescriptionPretty, Status } from './TransactionEdit'; +import { lookupName, Status } from './TransactionEdit'; const ROW_HEIGHT = 60; @@ -56,7 +56,7 @@ export function TransactionListItem({ const payee = usePayee(transaction?.payee || ''); const account = useAccount(transaction?.account || ''); - const transferAcct = useAccount(payee?.transfer_acct || ''); + const transferAccount = useAccount(payee?.transfer_acct || ''); const isPreview = isPreviewId(transaction?.id || ''); const newTransactions = useSelector(state => state.queries.newTransactions); @@ -84,32 +84,25 @@ export function TransactionListItem({ const { id, - amount: originalAmount, + amount, category: categoryId, cleared: isCleared, reconciled: isReconciled, is_parent: isParent, is_child: isChild, - schedule, + schedule: scheduleId, } = transaction; const isAdded = newTransactions.includes(id); - - let amount = originalAmount; - if (isPreview) { - amount = getScheduledAmount(amount); - } - const categoryName = lookupName(categories, categoryId); - - const prettyDescription = getDescriptionPretty( + const prettyPayee = getPrettyPayee({ transaction, payee, - transferAcct, - ); + transferAccount, + }); const specialCategory = account?.offbudget ? 'Off Budget' - : transferAcct && !transferAcct.offbudget + : transferAccount && !transferAccount.offbudget ? 'Transfer' : isParent ? 'Split' @@ -169,7 +162,7 @@ export function TransactionListItem({ > - {schedule && ( + {scheduleId && ( - {prettyDescription || 'Empty'} + {prettyPayee || 'No Payee'} {isPreview ? ( diff --git a/packages/desktop-client/src/components/mobile/utils.ts b/packages/desktop-client/src/components/mobile/utils.ts new file mode 100644 index 00000000000..a2a80c4f69b --- /dev/null +++ b/packages/desktop-client/src/components/mobile/utils.ts @@ -0,0 +1,31 @@ +import { + type AccountEntity, + type PayeeEntity, + type TransactionEntity, +} from 'loot-core/types/models'; + +type GetPrettyPayeeProps = { + transaction?: TransactionEntity; + payee?: PayeeEntity; + transferAccount?: AccountEntity; +}; + +export function getPrettyPayee({ + transaction, + payee, + transferAccount, +}: GetPrettyPayeeProps) { + if (!transaction) { + return ''; + } + + if (transferAccount) { + return `Transfer ${transaction?.amount > 0 ? 'from' : 'to'} ${transferAccount.name}`; + } else if (transaction.is_parent) { + return 'Split'; + } else if (payee) { + return payee.name; + } + + return '(No payee)'; +} diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 5f0f8334ff3..f2195155b2e 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -31,7 +31,6 @@ import { import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; import { currentDay } from 'loot-core/src/shared/months'; import * as monthUtils from 'loot-core/src/shared/months'; -import { getScheduledAmount } from 'loot-core/src/shared/schedules'; import { splitTransaction, updateTransaction, @@ -94,11 +93,7 @@ function getDisplayValue(obj, name) { } function serializeTransaction(transaction, showZeroInDeposit) { - let { amount, date } = transaction; - - if (isPreviewId(transaction.id)) { - amount = (transaction._inverse ? -1 : 1) * getScheduledAmount(amount); - } + const { amount, date: originalDate } = transaction; let debit = amount < 0 ? -amount : null; let credit = amount > 0 ? amount : null; @@ -111,6 +106,7 @@ function serializeTransaction(transaction, showZeroInDeposit) { } } + let date = originalDate; // Validate the date format if (!isDateValid(parseISO(date))) { // Be a little forgiving if the date isn't valid. This at least @@ -364,7 +360,7 @@ function getPayeePretty(transaction, payee, transferAcct, numHiddenPayees = 0) { return formatPayeeName(payeeId.slice('new:'.length)); } - return ''; + return '(No payee)'; } function StatusCell({ @@ -750,6 +746,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,22 +763,6 @@ function PayeeIcons({ onNavigateToSchedule, }) { const scheduleId = transaction.schedule; - - 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 }), []); - const { isLoading, schedules = [] } = useCachedSchedules(); if (isLoading) { @@ -787,6 +777,7 @@ function PayeeIcons({ } const recurring = schedule && schedule._date && !!schedule._date.frequency; + const isDeposit = transaction.amount > 0; return ( <> @@ -794,7 +785,7 @@ function PayeeIcons({