-
Matiss Janis Aboltins authored
*
(mobile) pull down to trigger bank-sync * Release notes * Remove canSync checksMatiss Janis Aboltins authored*
(mobile) pull down to trigger bank-sync * Release notes * Remove canSync checks
MobileAccount.js 6.17 KiB
import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams, useNavigate } from 'react-router-dom';
import debounce from 'debounce';
import memoizeOne from 'memoize-one';
import { bindActionCreators } from 'redux';
import * as actions from 'loot-core/src/client/actions';
import {
SchedulesProvider,
useCachedSchedules,
} from 'loot-core/src/client/data-hooks/schedules';
import * as queries from 'loot-core/src/client/queries';
import { pagedQuery } from 'loot-core/src/client/query-helpers';
import { listen } from 'loot-core/src/platform/client/fetch';
import {
isPreviewId,
ungroupTransactions,
} from 'loot-core/src/shared/transactions';
import useCategories from '../../hooks/useCategories';
import { useSetThemeColor } from '../../hooks/useSetThemeColor';
import { theme } from '../../style';
import AccountDetails from './MobileAccountDetails';
const getSchedulesTransform = memoizeOne((id, hasSearch) => {
let filter = queries.getAccountFilter(id, '_account');
// Never show schedules on these pages
if (hasSearch) {
filter = { id: null };
}
return q => {
q = q.filter({ $and: [filter, { '_account.closed': false }] });
return q.orderBy({ next_date: 'desc' });
};
});
function PreviewTransactions({ accountId, children }) {
let scheduleData = useCachedSchedules();
if (scheduleData == null) {
return children(null);
}
let schedules = scheduleData.schedules.filter(
s =>
!s.completed &&
['due', 'upcoming', 'missed'].includes(scheduleData.statuses.get(s.id)),
);
return children(
schedules.map(schedule => ({
id: 'preview/' + schedule.id,
payee: schedule._payee,
account: schedule._account,
amount: schedule._amount,
date: schedule.next_date,
notes: scheduleData.statuses.get(schedule.id),
schedule: schedule.id,
})),
);
}
let paged;
export default function Account(props) {
const accounts = useSelector(state => state.queries.accounts);
const navigate = useNavigate();
const [transactions, setTransactions] = useState([]);
const [searchText, setSearchText] = useState('');
const [currentQuery, setCurrentQuery] = useState();
let state = useSelector(state => ({
payees: state.queries.payees,
newTransactions: state.queries.newTransactions,
prefs: state.prefs.local,
dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy',
}));
let dispatch = useDispatch();
let actionCreators = useMemo(
() => bindActionCreators(actions, dispatch),
[dispatch],
);
const { id: accountId } = useParams();
const makeRootQuery = () => queries.makeTransactionsQuery(accountId);
const updateQuery = query => {
if (paged) {
paged.unsubscribe();
}
paged = pagedQuery(
query.options({ splits: 'grouped' }).select('*'),
data => setTransactions(data),
{ pageCount: 150, mapper: ungroupTransactions },
);
};
const fetchTransactions = async () => {
let query = makeRootQuery();
setCurrentQuery(query);
updateQuery(query);
};
useEffect(() => {
let unlisten;
async function setUpAccount() {
unlisten = listen('sync-event', ({ type, tables }) => {
if (type === 'applied') {
if (
tables.includes('transactions') ||
tables.includes('category_mapping') ||
tables.includes('payee_mapping')
) {
paged?.run();
}
if (tables.includes('payees') || tables.includes('payee_mapping')) {
actionCreators.getPayees();
}
}
});
if (accounts.length === 0) {
await actionCreators.getAccounts();
}
await actionCreators.initiallyLoadPayees();
await fetchTransactions();
actionCreators.markAccountRead(accountId);
}
setUpAccount();
return () => unlisten();
}, []);
// Load categories if necessary.
const categories = useCategories();
const updateSearchQuery = debounce(() => {
if (searchText === '' && currentQuery) {
updateQuery(currentQuery);
} else if (searchText && currentQuery) {
updateQuery(
queries.makeTransactionSearchQuery(
currentQuery,
searchText,
state.dateFormat,
),
);
}
}, 150);
useEffect(updateSearchQuery, [searchText, currentQuery, state.dateFormat]);
useSetThemeColor(theme.mobileAccountViewTheme);
if (!accounts || !accounts.length) {
return null;
}
const account = accounts.find(acct => acct.id === accountId);
const isNewTransaction = id => {
return state.newTransactions.includes(id);
};
const onSearch = async text => {
paged.unsubscribe();
setSearchText(text);
};
const onSelectTransaction = transaction => {
// details of how the native app used to handle preview transactions here can be found at commit 05e58279
if (!isPreviewId(transaction.id)) {
navigate(`transactions/${transaction.id}`);
}
};
let balance = queries.accountBalance(account);
let numberFormat = state.prefs.numberFormat || 'comma-dot';
let hideFraction = state.prefs.hideFraction || false;
return (
<SchedulesProvider
transform={getSchedulesTransform(accountId, searchText !== '')}
>
<PreviewTransactions accountId={props.accountId}>
{prependTransactions =>
prependTransactions == null ? null : (
<AccountDetails
// This key forces the whole table rerender when the number
// format changes
{...state}
{...actionCreators}
key={numberFormat + hideFraction}
account={account}
accounts={accounts}
categories={categories.list}
payees={state.payees}
transactions={transactions}
prependTransactions={prependTransactions || []}
balance={balance}
isNewTransaction={isNewTransaction}
onLoadMore={() => {
paged?.fetchNext();
}}
onSearch={onSearch}
onSelectTransaction={onSelectTransaction}
/>
)
}
</PreviewTransactions>
</SchedulesProvider>
);
}