Add in mobile running balance (#5513)

* Revert "Revert "Mobile running balance" because its causing extreme slowness …"

This reverts commit 6e2154d401.

* Fix rebase error

* Release notes

* [autofix.ci] apply automated fixes

* Cleanup

* Rename some props

* Delete upcoming-release-notes/5219.md

* Do not calculate running balance if searching or non-account

* Temporarily increase timeout

* Do not show running balance on non-account views

* Fix merge error

* Fix lint

* Coderabbit feedback

* Update TransactionList dependencies

* Remove test.slow()

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Joel Jeremy Marquez <joeljeremy.marquez@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
youngcw
2025-11-11 16:54:04 -07:00
committed by GitHub
parent e64a042f0c
commit cac4be7d38
9 changed files with 131 additions and 21 deletions

View File

@@ -27,6 +27,7 @@ import { useAccount } from '@desktop-client/hooks/useAccount';
import { useFailedAccounts } from '@desktop-client/hooks/useFailedAccounts';
import { useSyncedPref } from '@desktop-client/hooks/useSyncedPref';
import {
collapseModals,
openAccountCloseModal,
pushModal,
} from '@desktop-client/modals/modalsSlice';
@@ -145,6 +146,18 @@ function AccountHeader({ account }: { readonly account: AccountEntity }) {
dispatch(reopenAccount({ id: account.id }));
}, [account.id, dispatch]);
const [showRunningBalances, setShowRunningBalances] = useSyncedPref(
`show-balances-${account.id}`,
);
const onToggleRunningBalance = useCallback(() => {
setShowRunningBalances(showRunningBalances === 'true' ? 'false' : 'true');
dispatch(
collapseModals({
rootModalName: 'account-menu',
}),
);
}, [showRunningBalances, setShowRunningBalances, dispatch]);
const onClick = useCallback(() => {
dispatch(
pushModal({
@@ -156,6 +169,7 @@ function AccountHeader({ account }: { readonly account: AccountEntity }) {
onEditNotes,
onCloseAccount,
onReopenAccount,
onToggleRunningBalance,
},
},
}),
@@ -167,6 +181,7 @@ function AccountHeader({ account }: { readonly account: AccountEntity }) {
onEditNotes,
onReopenAccount,
onSave,
onToggleRunningBalance,
]);
return (

View File

@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { listen, send } from 'loot-core/platform/client/fetch';
import { type Query } from 'loot-core/shared/query';
import { isPreviewId } from 'loot-core/shared/transactions';
import { type IntegerAmount } from 'loot-core/shared/util';
import {
type AccountEntity,
type TransactionEntity,
@@ -17,6 +18,7 @@ import { SchedulesProvider } from '@desktop-client/hooks/useCachedSchedules';
import { useDateFormat } from '@desktop-client/hooks/useDateFormat';
import { useNavigate } from '@desktop-client/hooks/useNavigate';
import { getSchedulesQuery } from '@desktop-client/hooks/useSchedules';
import { useSyncedPref } from '@desktop-client/hooks/useSyncedPref';
import { useTransactions } from '@desktop-client/hooks/useTransactions';
import { useTransactionsSearch } from '@desktop-client/hooks/useTransactionsSearch';
import { collapseModals, pushModal } from '@desktop-client/modals/modalsSlice';
@@ -47,6 +49,9 @@ function TransactionListWithPreviews({
readonly account: AccountEntity;
}) {
const { t } = useTranslation();
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
const dispatch = useDispatch();
const navigate = useNavigate();
const baseTransactionsQuery = useCallback(
() =>
@@ -54,27 +59,41 @@ function TransactionListWithPreviews({
[account.id],
);
const [showRunningBalances] = useSyncedPref(`show-balances-${account.id}`);
const [transactionsQuery, setTransactionsQuery] = useState<Query>(
baseTransactionsQuery(),
);
const { isSearching, search: onSearch } = useTransactionsSearch({
updateQuery: setTransactionsQuery,
resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
dateFormat,
});
const shouldCalculateRunningBalances =
showRunningBalances === 'true' && !!account?.id && !isSearching;
const {
transactions,
runningBalances,
isLoading: isTransactionsLoading,
reload: reloadTransactions,
isLoadingMore,
loadMore: loadMoreTransactions,
} = useTransactions({
query: transactionsQuery,
options: {
calculateRunningBalances: shouldCalculateRunningBalances,
},
});
const { previewTransactions, isLoading: isPreviewTransactionsLoading } =
useAccountPreviewTransactions({
accountId: account?.id,
});
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
const dispatch = useDispatch();
const navigate = useNavigate();
const {
previewTransactions,
runningBalances: previewRunningBalances,
isLoading: isPreviewTransactionsLoading,
} = useAccountPreviewTransactions({
accountId: account?.id,
});
const onRefresh = useCallback(() => {
if (account.id) {
@@ -82,6 +101,15 @@ function TransactionListWithPreviews({
}
}, [account.id, dispatch]);
const allBalances = useMemo(
() =>
new Map<TransactionEntity['id'], IntegerAmount>([
...previewRunningBalances,
...runningBalances,
]),
[runningBalances, previewRunningBalances],
);
useEffect(() => {
if (account.id) {
dispatch(markAccountRead({ id: account.id }));
@@ -103,12 +131,6 @@ function TransactionListWithPreviews({
});
}, [dispatch, reloadTransactions]);
const { isSearching, search: onSearch } = useTransactionsSearch({
updateQuery: setTransactionsQuery,
resetQuery: () => setTransactionsQuery(baseTransactionsQuery()),
dateFormat,
});
const onOpenTransaction = useCallback(
(transaction: TransactionEntity) => {
if (!isPreviewId(transaction.id)) {
@@ -178,12 +200,16 @@ function TransactionListWithPreviews({
return (
<TransactionListWithBalances
isLoading={
isSearching ? isTransactionsLoading : isPreviewTransactionsLoading
isSearching
? isTransactionsLoading
: isTransactionsLoading || isPreviewTransactionsLoading
}
transactions={transactionsToDisplay}
balance={balanceBindings.balance}
balanceCleared={balanceBindings.cleared}
balanceUncleared={balanceBindings.uncleared}
runningBalances={allBalances}
showRunningBalances={shouldCalculateRunningBalances}
isLoadingMore={isLoadingMore}
onLoadMore={loadMoreTransactions}
searchPlaceholder={t('Search {{accountName}}', {

View File

@@ -123,7 +123,9 @@ function TransactionListWithPreviews({
return (
<TransactionListWithBalances
isLoading={
isSearching ? isTransactionsLoading : isPreviewTransactionsLoading
isSearching
? isTransactionsLoading
: isTransactionsLoading || isPreviewTransactionsLoading
}
transactions={transactionsToDisplay}
balance={balance}

View File

@@ -34,7 +34,11 @@ import { View } from '@actual-app/components/view';
import * as monthUtils from 'loot-core/shared/months';
import { isPreviewId } from 'loot-core/shared/transactions';
import { validForTransfer } from 'loot-core/shared/transfer';
import { groupById, integerToCurrency } from 'loot-core/shared/util';
import {
groupById,
type IntegerAmount,
integerToCurrency,
} from 'loot-core/shared/util';
import { type TransactionEntity } from 'loot-core/types/models';
import { ROW_HEIGHT, TransactionListItem } from './TransactionListItem';
@@ -83,6 +87,8 @@ function Loading({ style, 'aria-label': ariaLabel }: LoadingProps) {
type TransactionListProps = {
isLoading: boolean;
transactions: readonly TransactionEntity[];
showRunningBalances?: boolean;
runningBalances?: Map<TransactionEntity['id'], IntegerAmount>;
onOpenTransaction?: (transaction: TransactionEntity) => void;
isLoadingMore: boolean;
onLoadMore: () => void;
@@ -92,6 +98,8 @@ type TransactionListProps = {
export function TransactionList({
isLoading,
transactions,
showRunningBalances,
runningBalances,
onOpenTransaction,
isLoadingMore,
onLoadMore,
@@ -174,7 +182,14 @@ export function TransactionList({
selectedTransactions.size > 0 ? 'multiple' : 'single'
}
selectedKeys={selectedTransactions}
dependencies={[selectedTransactions]}
dependencies={[
selectedTransactions,
locale,
onTransactionPress,
runningBalances,
showRunningBalances,
t,
]}
renderEmptyState={() =>
!isLoading && (
<View
@@ -219,6 +234,8 @@ export function TransactionList({
{transaction => (
<TransactionListItem
key={transaction.id}
showRunningBalance={showRunningBalances}
runningBalance={runningBalances?.get(transaction.id)}
value={transaction}
onPress={trans => onTransactionPress(trans)}
onLongPress={trans => onTransactionPress(trans, true)}

View File

@@ -30,7 +30,7 @@ import {
} from '@react-aria/interactions';
import { isPreviewId } from 'loot-core/shared/transactions';
import { integerToCurrency } from 'loot-core/shared/util';
import { type IntegerAmount, integerToCurrency } from 'loot-core/shared/util';
import {
type AccountEntity,
type TransactionEntity,
@@ -38,7 +38,10 @@ import {
import { lookupName, Status } from './TransactionEdit';
import { makeAmountFullStyle } from '@desktop-client/components/budget/util';
import {
makeAmountFullStyle,
makeBalanceAmountStyle,
} from '@desktop-client/components/budget/util';
import { useAccount } from '@desktop-client/hooks/useAccount';
import { useCachedSchedules } from '@desktop-client/hooks/useCachedSchedules';
import { useCategories } from '@desktop-client/hooks/useCategories';
@@ -75,11 +78,15 @@ type TransactionListItemProps = Omit<
ComponentPropsWithoutRef<typeof ListBoxItem<TransactionEntity>>,
'onPress'
> & {
showRunningBalance?: boolean;
runningBalance?: IntegerAmount;
onPress: (transaction: TransactionEntity) => void;
onLongPress: (transaction: TransactionEntity) => void;
};
export function TransactionListItem({
showRunningBalance,
runningBalance,
onPress,
onLongPress,
...props
@@ -286,7 +293,9 @@ export function TransactionListItem({
</TextOneLine>
)}
</View>
<View style={{ justifyContent: 'center' }}>
<View
style={{ justifyContent: 'center', alignItems: 'flex-end' }}
>
<Text
style={{
...textStyle,
@@ -295,6 +304,17 @@ export function TransactionListItem({
>
{integerToCurrency(amount)}
</Text>
{showRunningBalance && runningBalance !== undefined && (
<Text
style={{
fontSize: 11,
fontWeight: '400',
...makeBalanceAmountStyle(runningBalance),
}}
>
{integerToCurrency(runningBalance)}
</Text>
)}
</View>
</View>
</Button>

View File

@@ -6,6 +6,7 @@ import { styles } from '@actual-app/components/styles';
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';
import { type IntegerAmount } from 'loot-core/shared/util';
import { type TransactionEntity } from 'loot-core/types/models';
import { TransactionList } from './TransactionList';
@@ -84,6 +85,8 @@ type TransactionListWithBalancesProps = {
balanceUncleared?:
| Binding<'category', 'balanceUncleared'>
| Binding<'account', 'balanceUncleared'>;
showRunningBalances?: boolean;
runningBalances?: Map<TransactionEntity['id'], IntegerAmount>;
searchPlaceholder: string;
onSearch: (searchText: string) => void;
isLoadingMore: boolean;
@@ -99,6 +102,8 @@ export function TransactionListWithBalances({
balance,
balanceCleared,
balanceUncleared,
showRunningBalances,
runningBalances,
searchPlaceholder = 'Search...',
onSearch,
isLoadingMore,
@@ -146,6 +151,8 @@ export function TransactionListWithBalances({
<TransactionList
isLoading={isLoading}
transactions={transactions}
showRunningBalances={showRunningBalances}
runningBalances={runningBalances}
isLoadingMore={isLoadingMore}
onLoadMore={onLoadMore}
onOpenTransaction={onOpenTransaction}

View File

@@ -33,6 +33,7 @@ import { validateAccountName } from '@desktop-client/components/util/accountVali
import { useAccount } from '@desktop-client/hooks/useAccount';
import { useAccounts } from '@desktop-client/hooks/useAccounts';
import { useNotes } from '@desktop-client/hooks/useNotes';
import { useSyncedPref } from '@desktop-client/hooks/useSyncedPref';
import { type Modal as ModalType } from '@desktop-client/modals/modalsSlice';
type AccountMenuModalProps = Extract<
@@ -47,6 +48,7 @@ export function AccountMenuModal({
onReopenAccount,
onEditNotes,
onClose,
onToggleRunningBalance,
}: AccountMenuModalProps) {
const { t } = useTranslation();
const account = useAccount(accountId);
@@ -124,6 +126,7 @@ export function AccountMenuModal({
account={account}
onClose={onCloseAccount}
onReopen={onReopenAccount}
onToggleRunningBalance={onToggleRunningBalance}
/>
}
title={
@@ -201,12 +204,14 @@ type AdditionalAccountMenuProps = {
account: AccountEntity;
onClose?: (accountId: string) => void;
onReopen?: (accountId: string) => void;
onToggleRunningBalance?: () => void;
};
function AdditionalAccountMenu({
account,
onClose,
onReopen,
onToggleRunningBalance,
}: AdditionalAccountMenuProps) {
const { t } = useTranslation();
const triggerRef = useRef(null);
@@ -220,6 +225,7 @@ function AdditionalAccountMenu({
...itemStyle,
...(item.name === 'close' && { color: theme.errorTextMenu }),
});
const [showBalances] = useSyncedPref(`show-balances-${account.id}`);
return (
<View>
@@ -245,6 +251,13 @@ function AdditionalAccountMenu({
<Menu
getItemStyle={getItemStyle}
items={[
{
name: 'balance',
text:
showBalances === 'true'
? t('Hide running balance')
: t('Show running balance'),
},
account.closed
? {
name: 'reopen',
@@ -268,6 +281,9 @@ function AdditionalAccountMenu({
case 'reopen':
onReopen?.(account.id);
break;
case 'balance':
onToggleRunningBalance?.();
break;
default:
throw new Error(`Unrecognized menu option: ${name}`);
}

View File

@@ -280,6 +280,7 @@ export type Modal =
onReopenAccount: (accountId: AccountEntity['id']) => void;
onEditNotes: (id: NoteEntity['id']) => void;
onClose?: () => void;
onToggleRunningBalance?: () => void;
};
}
| {

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [youngcw,joel-jeremy]
---
Add back mobile running balance functionality