actual/packages/desktop-client/src/components/accounts/TransactionList.js
Tom French 9c0df36e16
Sort import in alphabetical order (#238)
* style: enforce sorting of imports

* style: alphabetize imports

* style: merge duplicated imports
2022-09-02 15:07:24 +01:00

197 lines
5.6 KiB
JavaScript

import React, { useRef, useEffect, useCallback, useLayoutEffect } from 'react';
import { useDispatch } from 'react-redux';
import { send } from 'loot-core/src/platform/client/fetch';
import {
splitTransaction,
updateTransaction,
addSplitTransaction,
realizeTempTransactions,
applyTransactionDiff
} from 'loot-core/src/shared/transactions';
import { getChangedValues, applyChanges } from 'loot-core/src/shared/util';
import { TransactionTable } from './TransactionsTable';
const uuid = require('loot-core/src/platform/uuid');
// When data changes, there are two ways to update the UI:
//
// * Optimistic updates: we apply the needed updates to local data
// and rerender immediately, and send off the changes to the
// server. Currently, it assumes the server request is successful.
// If it fails the user will see a generic error which isn't
// great, but since the server is local a failure is very
// unlikely. Still, we should notify errors better.
//
// * A full refetch and rerender: this is needed when applying
// updates locally is too complex. Usually this happens when
// changing a field that data is sorted on: we're not going
// to resort the data in memory, we want to rely on the database
// for that. So we need to do a full refresh.
//
// When writing updates, it's up to you to decide which one to do.
// Optimistic updates feel snappy, but they might show data
// differently than a full refresh. It's up to you to decide which
// one to use when doing updates.
async function saveDiff(diff) {
let remoteUpdates = await send('transactions-batch-update', {
...diff,
learnCategories: true
});
if (remoteUpdates.length > 0) {
return { updates: remoteUpdates };
}
return {};
}
async function saveDiffAndApply(diff, changes, onChange) {
let remoteDiff = await saveDiff(diff);
onChange(
applyTransactionDiff(changes.newTransaction, remoteDiff),
applyChanges(remoteDiff, changes.data)
);
}
export default function TransactionList({
tableRef,
transactions,
allTransactions,
loadMoreTransactions,
account,
accounts,
categoryGroups,
payees,
balances,
showAccount,
headerContent,
animated,
isAdding,
isNew,
isMatched,
isFiltered,
dateFormat,
addNotification,
renderEmpty,
onChange,
onRefetch,
onRefetchUpToRow,
onCloseAddTransaction,
onManagePayees,
onCreatePayee
}) {
let dispatch = useDispatch();
let table = useRef();
let transactionsLatest = useRef();
let scrollTo = useRef();
// useEffect(() => {
// if (scrollTo.current) {
// // table.current.scrollTo(scrollTo.current);
// }
// }, [transactions]);
useEffect(clearScrollTo);
useLayoutEffect(() => {
transactionsLatest.current = transactions;
}, [transactions]);
function clearScrollTo() {
scrollTo.current = null;
}
let onAdd = useCallback(async newTransactions => {
newTransactions = realizeTempTransactions(newTransactions);
await saveDiff({ added: newTransactions });
onRefetch();
}, []);
let onSave = useCallback(async transaction => {
let changes = updateTransaction(transactionsLatest.current, transaction);
if (changes.diff.updated.length > 0) {
let dateChanged = !!changes.diff.updated[0].date;
if (dateChanged) {
// Make sure it stays at the top of the list of transactions
// for that date
changes.diff.updated[0].sort_order = Date.now();
await saveDiff(changes.diff);
onRefetch();
} else {
onChange(changes.newTransaction, changes.data);
saveDiffAndApply(changes.diff, changes, onChange);
}
}
}, []);
let onAddSplit = useCallback(id => {
const changes = addSplitTransaction(transactionsLatest.current, id);
onChange(changes.newTransaction, changes.data);
saveDiffAndApply(changes.diff, changes, onChange);
return changes.diff.added[0].id;
}, []);
let onSplit = useCallback(id => {
const changes = splitTransaction(transactionsLatest.current, id);
onChange(changes.newTransaction, changes.data);
saveDiffAndApply(changes.diff, changes, onChange);
return changes.diff.added[0].id;
}, []);
let onApplyRules = useCallback(async transaction => {
let afterRules = await send('rules-run', { transaction });
let diff = getChangedValues(transaction, afterRules);
let newTransaction = { ...transaction };
if (diff) {
Object.keys(diff).forEach(field => {
if (
newTransaction[field] == null ||
newTransaction[field] === '' ||
newTransaction[field] === 0
) {
newTransaction[field] = diff[field];
}
});
}
return newTransaction;
}, []);
return (
<TransactionTable
ref={tableRef}
transactions={allTransactions}
loadMoreTransactions={loadMoreTransactions}
accounts={accounts}
categoryGroups={categoryGroups}
payees={payees}
balances={balances}
showAccount={showAccount}
showCategory={true}
animated={animated}
currentAccountId={account && account.id}
isAdding={isAdding}
isNew={isNew}
isMatched={isMatched}
isFiltered={isFiltered}
dateFormat={dateFormat}
addNotification={addNotification}
headerContent={headerContent}
renderEmpty={renderEmpty}
onSave={onSave}
onApplyRules={onApplyRules}
onSplit={onSplit}
onCloseAddTransaction={onCloseAddTransaction}
onAdd={onAdd}
onAddSplit={onAddSplit}
onManagePayees={onManagePayees}
onCreatePayee={onCreatePayee}
onScroll={clearScrollTo}
style={{ backgroundColor: 'white' }}
/>
);
}