mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
⚡ 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:
@@ -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 (
|
||||
|
||||
@@ -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}}', {
|
||||
|
||||
@@ -123,7 +123,9 @@ function TransactionListWithPreviews({
|
||||
return (
|
||||
<TransactionListWithBalances
|
||||
isLoading={
|
||||
isSearching ? isTransactionsLoading : isPreviewTransactionsLoading
|
||||
isSearching
|
||||
? isTransactionsLoading
|
||||
: isTransactionsLoading || isPreviewTransactionsLoading
|
||||
}
|
||||
transactions={transactionsToDisplay}
|
||||
balance={balance}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -280,6 +280,7 @@ export type Modal =
|
||||
onReopenAccount: (accountId: AccountEntity['id']) => void;
|
||||
onEditNotes: (id: NoteEntity['id']) => void;
|
||||
onClose?: () => void;
|
||||
onToggleRunningBalance?: () => void;
|
||||
};
|
||||
}
|
||||
| {
|
||||
|
||||
6
upcoming-release-notes/5513.md
Normal file
6
upcoming-release-notes/5513.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [youngcw,joel-jeremy]
|
||||
---
|
||||
|
||||
Add back mobile running balance functionality
|
||||
Reference in New Issue
Block a user