mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
Compare commits
2 Commits
react-quer
...
useTransac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e40dbf2201 | ||
|
|
509a77c22f |
@@ -3,7 +3,6 @@ import React, {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
useRef,
|
useRef,
|
||||||
memo,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
@@ -19,12 +18,11 @@ import {
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
import { pushModal, setLastTransaction } from 'loot-core/client/actions';
|
import { pushModal, setLastTransaction } from 'loot-core/client/actions';
|
||||||
import { runQuery } from 'loot-core/src/client/query-helpers';
|
import { useTransactions } from 'loot-core/client/data-hooks/transactions';
|
||||||
import { send } from 'loot-core/src/platform/client/fetch';
|
import { send } from 'loot-core/src/platform/client/fetch';
|
||||||
import * as monthUtils from 'loot-core/src/shared/months';
|
import * as monthUtils from 'loot-core/src/shared/months';
|
||||||
import { q } from 'loot-core/src/shared/query';
|
import { q } from 'loot-core/src/shared/query';
|
||||||
import {
|
import {
|
||||||
ungroupTransactions,
|
|
||||||
updateTransaction,
|
updateTransaction,
|
||||||
realizeTempTransactions,
|
realizeTempTransactions,
|
||||||
splitTransaction,
|
splitTransaction,
|
||||||
@@ -52,6 +50,7 @@ import {
|
|||||||
SingleActiveEditFormProvider,
|
SingleActiveEditFormProvider,
|
||||||
useSingleActiveEditForm,
|
useSingleActiveEditForm,
|
||||||
} from '../../../hooks/useSingleActiveEditForm';
|
} from '../../../hooks/useSingleActiveEditForm';
|
||||||
|
import { AnimatedLoading } from '../../../icons/AnimatedLoading';
|
||||||
import { SvgSplit } from '../../../icons/v0';
|
import { SvgSplit } from '../../../icons/v0';
|
||||||
import { SvgAdd, SvgPiggyBank, SvgTrash } from '../../../icons/v1';
|
import { SvgAdd, SvgPiggyBank, SvgTrash } from '../../../icons/v1';
|
||||||
import { SvgPencilWriteAlternate } from '../../../icons/v2';
|
import { SvgPencilWriteAlternate } from '../../../icons/v2';
|
||||||
@@ -156,7 +155,7 @@ export function Status({ status, isSplit }) {
|
|||||||
|
|
||||||
function Footer({
|
function Footer({
|
||||||
transactions,
|
transactions,
|
||||||
adding,
|
isAdding,
|
||||||
onAdd,
|
onAdd,
|
||||||
onSave,
|
onSave,
|
||||||
onSplit,
|
onSplit,
|
||||||
@@ -232,7 +231,7 @@ function Footer({
|
|||||||
Select account
|
Select account
|
||||||
</Text>
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
) : adding ? (
|
) : isAdding ? (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ height: styles.mobileMinHeight }}
|
style={{ height: styles.mobileMinHeight }}
|
||||||
@@ -435,30 +434,188 @@ const ChildTransactionEdit = forwardRef(
|
|||||||
|
|
||||||
ChildTransactionEdit.displayName = 'ChildTransactionEdit';
|
ChildTransactionEdit.displayName = 'ChildTransactionEdit';
|
||||||
|
|
||||||
const TransactionEditInner = memo(function TransactionEditInner({
|
function isTemporary(transaction) {
|
||||||
adding,
|
return transaction.id.indexOf('temp') === 0;
|
||||||
accounts,
|
}
|
||||||
categories,
|
|
||||||
payees,
|
function makeTemporaryTransactions(accountId, categoryId, lastDate) {
|
||||||
dateFormat,
|
return [
|
||||||
transactions: unserializedTransactions,
|
{
|
||||||
onSave,
|
id: 'temp',
|
||||||
onUpdate,
|
date: lastDate || monthUtils.currentDay(),
|
||||||
onDelete,
|
account: accountId,
|
||||||
onSplit,
|
category: categoryId,
|
||||||
onAddSplit,
|
amount: 0,
|
||||||
}) {
|
cleared: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function TransactionEditInner() {
|
||||||
|
const { list: categories } = useCategories();
|
||||||
|
const payees = usePayees();
|
||||||
|
const lastTransaction = useSelector(state => state.queries.lastTransaction);
|
||||||
|
const accounts = useAccounts();
|
||||||
|
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
|
||||||
|
|
||||||
|
const { transactionId } = useParams();
|
||||||
|
const { state: locationState } = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const transactions = useMemo(
|
const isDeletedRef = useRef(false);
|
||||||
|
const isAddingRef = useRef(false);
|
||||||
|
isAddingRef.current = transactionId === 'new';
|
||||||
|
|
||||||
|
const transactionQuery = useMemo(
|
||||||
() =>
|
() =>
|
||||||
unserializedTransactions.map(t => serializeTransaction(t, dateFormat)) ||
|
transactionId !== null
|
||||||
[],
|
? q('transactions')
|
||||||
[unserializedTransactions, dateFormat],
|
.filter({ id: transactionId })
|
||||||
|
.select('*')
|
||||||
|
.options({ splits: 'all' })
|
||||||
|
: null,
|
||||||
|
[transactionId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { transactions: queriedTransactions, isLoading } = useTransactions({
|
||||||
|
query: transactionQuery,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [transactions, setTransactions] = useState(
|
||||||
|
makeTemporaryTransactions(
|
||||||
|
locationState?.accountId || lastTransaction?.account || null,
|
||||||
|
locationState?.categoryId || null,
|
||||||
|
lastTransaction?.date,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (queriedTransactions?.length > 0) {
|
||||||
|
setTransactions(queriedTransactions);
|
||||||
|
}
|
||||||
|
}, [queriedTransactions]);
|
||||||
|
|
||||||
|
const onUpdate = useCallback(
|
||||||
|
async (transaction, updatedField) => {
|
||||||
|
// Run the rules to auto-fill in any data. Right now we only do
|
||||||
|
// this on new transactions because that's how desktop works.
|
||||||
|
const newTransaction = { ...transaction };
|
||||||
|
if (isTemporary(newTransaction)) {
|
||||||
|
const afterRules = await send('rules-run', {
|
||||||
|
transaction: newTransaction,
|
||||||
|
});
|
||||||
|
const diff = getChangedValues(newTransaction, afterRules);
|
||||||
|
|
||||||
|
if (diff) {
|
||||||
|
Object.keys(diff).forEach(field => {
|
||||||
|
if (
|
||||||
|
newTransaction[field] == null ||
|
||||||
|
newTransaction[field] === '' ||
|
||||||
|
newTransaction[field] === 0 ||
|
||||||
|
newTransaction[field] === false
|
||||||
|
) {
|
||||||
|
newTransaction[field] = diff[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When a rule updates a parent transaction, overwrite all changes to the current field in subtransactions.
|
||||||
|
if (
|
||||||
|
newTransaction.is_parent &&
|
||||||
|
diff.subtransactions !== undefined &&
|
||||||
|
updatedField !== null
|
||||||
|
) {
|
||||||
|
newTransaction.subtransactions = diff.subtransactions.map(
|
||||||
|
(st, idx) => ({
|
||||||
|
...(newTransaction.subtransactions[idx] || st),
|
||||||
|
...(st[updatedField] != null && {
|
||||||
|
[updatedField]: st[updatedField],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changes = updateTransaction(transactions, newTransaction);
|
||||||
|
setTransactions(changes.data);
|
||||||
|
},
|
||||||
|
[transactions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSave = useCallback(
|
||||||
|
async newTransactions => {
|
||||||
|
if (isDeletedRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changes = diffItems(queriedTransactions || [], transactions);
|
||||||
|
if (
|
||||||
|
changes.added.length > 0 ||
|
||||||
|
changes.updated.length > 0 ||
|
||||||
|
changes.deleted.length > 0
|
||||||
|
) {
|
||||||
|
await send('transactions-batch-update', {
|
||||||
|
added: changes.added,
|
||||||
|
deleted: changes.deleted,
|
||||||
|
updated: changes.updated,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAddingRef.current) {
|
||||||
|
// The first one is always the "parent" and the only one we care
|
||||||
|
// about
|
||||||
|
dispatch(setLastTransaction(newTransactions[0]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, transactions, queriedTransactions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDelete = useCallback(
|
||||||
|
async id => {
|
||||||
|
const changes = deleteTransaction(transactions, id);
|
||||||
|
|
||||||
|
if (isAddingRef.current) {
|
||||||
|
// Adding a new transactions, this disables saving when the component unmounts
|
||||||
|
isDeletedRef.current = true;
|
||||||
|
} else {
|
||||||
|
await send('transactions-batch-update', {
|
||||||
|
deleted: changes.diff.deleted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransactions(changes.data);
|
||||||
|
},
|
||||||
|
[transactions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onAddSplit = useCallback(
|
||||||
|
id => {
|
||||||
|
const changes = addSplitTransaction(transactions, id);
|
||||||
|
setTransactions(changes.data);
|
||||||
|
},
|
||||||
|
[transactions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSplit = useCallback(
|
||||||
|
id => {
|
||||||
|
const changes = splitTransaction(transactions, id, parent => [
|
||||||
|
makeChild(parent),
|
||||||
|
makeChild(parent),
|
||||||
|
]);
|
||||||
|
|
||||||
|
setTransactions(changes.data);
|
||||||
|
},
|
||||||
|
[transactions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const serializedTransactions = useMemo(
|
||||||
|
() => transactions.map(t => serializeTransaction(t, dateFormat)) || [],
|
||||||
|
[transactions, dateFormat],
|
||||||
);
|
);
|
||||||
const { grouped: categoryGroups } = useCategories();
|
const { grouped: categoryGroups } = useCategories();
|
||||||
|
|
||||||
const [transaction, ...childTransactions] = transactions;
|
const [serializedTransaction, ...serializedChildTransactions] =
|
||||||
|
serializedTransactions;
|
||||||
|
|
||||||
const { editingField, onRequestActiveEdit, onClearActiveEdit } =
|
const { editingField, onRequestActiveEdit, onClearActiveEdit } =
|
||||||
useSingleActiveEditForm();
|
useSingleActiveEditForm();
|
||||||
@@ -470,19 +627,22 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
const accountsById = useMemo(() => groupById(accounts), [accounts]);
|
const accountsById = useMemo(() => groupById(accounts), [accounts]);
|
||||||
|
|
||||||
const onTotalAmountEdit = useCallback(() => {
|
const onTotalAmountEdit = useCallback(() => {
|
||||||
onRequestActiveEdit?.(getFieldName(transaction.id, 'amount'), () => {
|
onRequestActiveEdit?.(
|
||||||
|
getFieldName(serializedTransaction.id, 'amount'),
|
||||||
|
() => {
|
||||||
setTotalAmountFocused(true);
|
setTotalAmountFocused(true);
|
||||||
return () => setTotalAmountFocused(false);
|
return () => setTotalAmountFocused(false);
|
||||||
});
|
},
|
||||||
}, [onRequestActiveEdit, transaction.id]);
|
);
|
||||||
|
}, [onRequestActiveEdit, serializedTransaction.id]);
|
||||||
|
|
||||||
const isInitialMount = useInitialMount();
|
const isInitialMount = useInitialMount();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInitialMount && adding) {
|
if (isInitialMount && isAddingRef.current) {
|
||||||
onTotalAmountEdit();
|
onTotalAmountEdit();
|
||||||
}
|
}
|
||||||
}, [adding, isInitialMount, onTotalAmountEdit]);
|
}, [isInitialMount, onTotalAmountEdit]);
|
||||||
|
|
||||||
const getAccount = useCallback(
|
const getAccount = useCallback(
|
||||||
trans => {
|
trans => {
|
||||||
@@ -528,18 +688,20 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onSaveInner = useCallback(() => {
|
const onSaveInner = useCallback(() => {
|
||||||
const [unserializedTransaction] = unserializedTransactions;
|
const [transaction] = transactions;
|
||||||
|
|
||||||
const onConfirmSave = () => {
|
const onConfirmSave = () => {
|
||||||
let transactionsToSave = unserializedTransactions;
|
let transactionsToSave = transactions;
|
||||||
if (adding) {
|
if (isAddingRef.current) {
|
||||||
transactionsToSave = realizeTempTransactions(unserializedTransactions);
|
transactionsToSave = realizeTempTransactions(transactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(transactionsToSave);
|
onSave(transactionsToSave);
|
||||||
|
|
||||||
if (adding || hasAccountChanged.current) {
|
const isAddingFromAccountPage =
|
||||||
const { account: accountId } = unserializedTransaction;
|
isAddingRef.current && locationState?.accountId;
|
||||||
|
if (!isAddingFromAccountPage || hasAccountChanged.current) {
|
||||||
|
const { account: accountId } = transaction;
|
||||||
const account = accountsById?.[accountId];
|
const account = accountsById?.[accountId];
|
||||||
if (account) {
|
if (account) {
|
||||||
navigate(`/accounts/${account.id}`, { replace: true });
|
navigate(`/accounts/${account.id}`, { replace: true });
|
||||||
@@ -552,7 +714,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (unserializedTransaction.reconciled) {
|
if (transaction.reconciled) {
|
||||||
// On mobile any save gives the warning.
|
// On mobile any save gives the warning.
|
||||||
// On the web only certain changes trigger a warning.
|
// On the web only certain changes trigger a warning.
|
||||||
// Should we bring that here as well? Or does the nature of the editing form
|
// Should we bring that here as well? Or does the nature of the editing form
|
||||||
@@ -568,54 +730,63 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
accountsById,
|
accountsById,
|
||||||
adding,
|
|
||||||
dispatch,
|
dispatch,
|
||||||
|
locationState?.accountId,
|
||||||
navigate,
|
navigate,
|
||||||
onSave,
|
onSave,
|
||||||
unserializedTransactions,
|
transactions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onUpdateInner = useCallback(
|
const onUpdateInner = useCallback(
|
||||||
async (serializedTransaction, name, value) => {
|
async (serializedTransaction, name, value) => {
|
||||||
const newTransaction = { ...serializedTransaction, [name]: value };
|
const newTransaction = { ...serializedTransaction, [name]: value };
|
||||||
await onUpdate(newTransaction, name);
|
const transaction = deserializeTransaction(
|
||||||
|
newTransaction,
|
||||||
|
null,
|
||||||
|
dateFormat,
|
||||||
|
);
|
||||||
|
await onUpdate(transaction, name);
|
||||||
onClearActiveEdit();
|
onClearActiveEdit();
|
||||||
|
|
||||||
if (name === 'account') {
|
if (name === 'account') {
|
||||||
hasAccountChanged.current = serializedTransaction.account !== value;
|
hasAccountChanged.current = transaction.account !== value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onClearActiveEdit, onUpdate],
|
[dateFormat, onClearActiveEdit, onUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onTotalAmountUpdate = useCallback(
|
const onTotalAmountUpdate = useCallback(
|
||||||
value => {
|
value => {
|
||||||
if (transaction.amount !== value) {
|
if (serializedTransaction.amount !== value) {
|
||||||
onUpdateInner(transaction, 'amount', value.toString());
|
onUpdateInner(serializedTransaction, 'amount', value.toString());
|
||||||
} else {
|
} else {
|
||||||
onClearActiveEdit();
|
onClearActiveEdit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onClearActiveEdit, onUpdateInner, transaction],
|
[onClearActiveEdit, onUpdateInner, serializedTransaction],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onEditFieldInner = useCallback(
|
const onEditFieldInner = useCallback(
|
||||||
(transactionId, name) => {
|
(transactionId, name) => {
|
||||||
onRequestActiveEdit?.(getFieldName(transaction.id, name), () => {
|
onRequestActiveEdit?.(
|
||||||
const transactionToEdit = transactions.find(
|
getFieldName(serializedTransaction.id, name),
|
||||||
t => t.id === transactionId,
|
() => {
|
||||||
);
|
const serializedTransactionToEdit = serializedTransactions.find(
|
||||||
const unserializedTransaction = unserializedTransactions.find(
|
|
||||||
t => t.id === transactionId,
|
t => t.id === transactionId,
|
||||||
);
|
);
|
||||||
|
const transaction = transactions.find(t => t.id === transactionId);
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'category':
|
case 'category':
|
||||||
dispatch(
|
dispatch(
|
||||||
pushModal('category-autocomplete', {
|
pushModal('category-autocomplete', {
|
||||||
categoryGroups,
|
categoryGroups,
|
||||||
month: monthUtils.monthFromDate(unserializedTransaction.date),
|
month: monthUtils.monthFromDate(transaction.date),
|
||||||
onSelect: categoryId => {
|
onSelect: categoryId => {
|
||||||
onUpdateInner(transactionToEdit, name, categoryId);
|
onUpdateInner(
|
||||||
|
serializedTransactionToEdit,
|
||||||
|
name,
|
||||||
|
categoryId,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
onClearActiveEdit();
|
onClearActiveEdit();
|
||||||
@@ -627,7 +798,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
dispatch(
|
dispatch(
|
||||||
pushModal('account-autocomplete', {
|
pushModal('account-autocomplete', {
|
||||||
onSelect: accountId => {
|
onSelect: accountId => {
|
||||||
onUpdateInner(transactionToEdit, name, accountId);
|
onUpdateInner(serializedTransactionToEdit, name, accountId);
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
onClearActiveEdit();
|
onClearActiveEdit();
|
||||||
@@ -639,7 +810,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
dispatch(
|
dispatch(
|
||||||
pushModal('payee-autocomplete', {
|
pushModal('payee-autocomplete', {
|
||||||
onSelect: payeeId => {
|
onSelect: payeeId => {
|
||||||
onUpdateInner(transactionToEdit, name, payeeId);
|
onUpdateInner(serializedTransactionToEdit, name, payeeId);
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
onClearActiveEdit();
|
onClearActiveEdit();
|
||||||
@@ -651,9 +822,9 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
dispatch(
|
dispatch(
|
||||||
pushModal('edit-field', {
|
pushModal('edit-field', {
|
||||||
name,
|
name,
|
||||||
month: monthUtils.monthFromDate(unserializedTransaction.date),
|
month: monthUtils.monthFromDate(transaction.date),
|
||||||
onSubmit: (name, value) => {
|
onSubmit: (name, value) => {
|
||||||
onUpdateInner(transactionToEdit, name, value);
|
onUpdateInner(serializedTransactionToEdit, name, value);
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
onClearActiveEdit();
|
onClearActiveEdit();
|
||||||
@@ -662,23 +833,24 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
categoryGroups,
|
categoryGroups,
|
||||||
dispatch,
|
dispatch,
|
||||||
onUpdateInner,
|
|
||||||
onClearActiveEdit,
|
onClearActiveEdit,
|
||||||
onRequestActiveEdit,
|
onRequestActiveEdit,
|
||||||
transaction.id,
|
onUpdateInner,
|
||||||
|
serializedTransaction.id,
|
||||||
|
serializedTransactions,
|
||||||
transactions,
|
transactions,
|
||||||
unserializedTransactions,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDeleteInner = useCallback(
|
const onDeleteInner = useCallback(
|
||||||
id => {
|
id => {
|
||||||
const [unserializedTransaction] = unserializedTransactions;
|
const [transaction] = transactions;
|
||||||
|
|
||||||
const onConfirmDelete = () => {
|
const onConfirmDelete = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -686,7 +858,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
onDelete(id);
|
onDelete(id);
|
||||||
|
|
||||||
if (unserializedTransaction.id !== id) {
|
if (transaction.id !== id) {
|
||||||
// Only a child transaction was deleted.
|
// Only a child transaction was deleted.
|
||||||
onClearActiveEdit();
|
onClearActiveEdit();
|
||||||
return;
|
return;
|
||||||
@@ -698,7 +870,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (unserializedTransaction.reconciled) {
|
if (transaction.reconciled) {
|
||||||
dispatch(
|
dispatch(
|
||||||
pushModal('confirm-transaction-edit', {
|
pushModal('confirm-transaction-edit', {
|
||||||
onConfirm: onConfirmDelete,
|
onConfirm: onConfirmDelete,
|
||||||
@@ -709,7 +881,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
onConfirmDelete();
|
onConfirmDelete();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, navigate, onClearActiveEdit, onDelete, unserializedTransactions],
|
[dispatch, navigate, onClearActiveEdit, onDelete, transactions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollChildTransactionIntoView = useCallback(id => {
|
const scrollChildTransactionIntoView = useCallback(id => {
|
||||||
@@ -728,36 +900,60 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const noAmountChildTransaction = childTransactions.find(
|
const noAmountChildTransaction = serializedChildTransactions.find(
|
||||||
t => t.amount === 0,
|
t => t.amount === 0,
|
||||||
);
|
);
|
||||||
if (noAmountChildTransaction) {
|
if (noAmountChildTransaction) {
|
||||||
scrollChildTransactionIntoView(noAmountChildTransaction.id);
|
scrollChildTransactionIntoView(noAmountChildTransaction.id);
|
||||||
}
|
}
|
||||||
}, [childTransactions, scrollChildTransactionIntoView]);
|
}, [serializedChildTransactions, scrollChildTransactionIntoView]);
|
||||||
|
|
||||||
// Child transactions should always default to the signage
|
// Child transactions should always default to the signage
|
||||||
// of the parent transaction
|
// of the parent transaction
|
||||||
const childAmountSign = transaction.amount <= 0 ? '-' : '+';
|
const childAmountSign = serializedTransaction.amount <= 0 ? '-' : '+';
|
||||||
|
|
||||||
const account = getAccount(transaction);
|
const account = getAccount(serializedTransaction);
|
||||||
const isOffBudget = account && !!account.offbudget;
|
const isOffBudget = account && !!account.offbudget;
|
||||||
const title = getPrettyPayee({
|
const title = getPrettyPayee({
|
||||||
transaction,
|
transaction: serializedTransaction,
|
||||||
payee: getPayee(transaction),
|
payee: getPayee(serializedTransaction),
|
||||||
transferAccount: getTransferAccount(transaction),
|
transferAccount: getTransferAccount(serializedTransaction),
|
||||||
});
|
});
|
||||||
|
|
||||||
const transactionDate = parseDate(transaction.date, dateFormat, new Date());
|
const transactionDate = parseDate(
|
||||||
|
serializedTransaction.date,
|
||||||
|
dateFormat,
|
||||||
|
new Date(),
|
||||||
|
);
|
||||||
const dateDefaultValue = monthUtils.dayFromDate(transactionDate);
|
const dateDefaultValue = monthUtils.dayFromDate(transactionDate);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isLoading ||
|
||||||
|
categories.length === 0 ||
|
||||||
|
accounts.length === 0 ||
|
||||||
|
transactions.length === 0
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
aria-label={t('Loading...')}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AnimatedLoading width={25} height={25} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
header={
|
header={
|
||||||
<MobilePageHeader
|
<MobilePageHeader
|
||||||
title={
|
title={
|
||||||
transaction.payee == null
|
serializedTransaction.payee == null
|
||||||
? adding
|
? isAddingRef.current
|
||||||
? 'New Transaction'
|
? 'New Transaction'
|
||||||
: 'Transaction'
|
: 'Transaction'
|
||||||
: title
|
: title
|
||||||
@@ -772,7 +968,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
footer={
|
footer={
|
||||||
<Footer
|
<Footer
|
||||||
transactions={transactions}
|
transactions={transactions}
|
||||||
adding={adding}
|
isAdding={isAddingRef.current}
|
||||||
onAdd={onSaveInner}
|
onAdd={onSaveInner}
|
||||||
onSave={onSaveInner}
|
onSave={onSaveInner}
|
||||||
onSplit={onSplit}
|
onSplit={onSplit}
|
||||||
@@ -792,7 +988,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
>
|
>
|
||||||
<FieldLabel title={t('Amount')} flush style={{ marginBottom: 0 }} />
|
<FieldLabel title={t('Amount')} flush style={{ marginBottom: 0 }} />
|
||||||
<FocusableAmountInput
|
<FocusableAmountInput
|
||||||
value={transaction.amount}
|
value={serializedTransaction.amount}
|
||||||
zeroSign="-"
|
zeroSign="-"
|
||||||
focused={totalAmountFocused}
|
focused={totalAmountFocused}
|
||||||
onFocus={onTotalAmountEdit}
|
onFocus={onTotalAmountEdit}
|
||||||
@@ -812,7 +1008,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
<FieldLabel title={t('Payee')} />
|
<FieldLabel title={t('Payee')} />
|
||||||
<TapField
|
<TapField
|
||||||
textStyle={{
|
textStyle={{
|
||||||
...(transaction.is_parent && {
|
...(serializedTransaction.is_parent && {
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
}),
|
}),
|
||||||
@@ -820,38 +1016,42 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
value={title}
|
value={title}
|
||||||
disabled={
|
disabled={
|
||||||
editingField &&
|
editingField &&
|
||||||
editingField !== getFieldName(transaction.id, 'payee')
|
editingField !== getFieldName(serializedTransaction.id, 'payee')
|
||||||
}
|
}
|
||||||
onClick={() => onEditFieldInner(transaction.id, 'payee')}
|
onClick={() => onEditFieldInner(serializedTransaction.id, 'payee')}
|
||||||
data-testid="payee-field"
|
data-testid="payee-field"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{!transaction.is_parent && (
|
{!serializedTransaction.is_parent && (
|
||||||
<View>
|
<View>
|
||||||
<FieldLabel title={t('Category')} />
|
<FieldLabel title={t('Category')} />
|
||||||
<TapField
|
<TapField
|
||||||
style={{
|
style={{
|
||||||
...((isOffBudget || isBudgetTransfer(transaction)) && {
|
...((isOffBudget ||
|
||||||
|
isBudgetTransfer(serializedTransaction)) && {
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
color: theme.pageTextSubdued,
|
color: theme.pageTextSubdued,
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
value={getCategory(transaction, isOffBudget)}
|
value={getCategory(serializedTransaction, isOffBudget)}
|
||||||
disabled={
|
disabled={
|
||||||
(editingField &&
|
(editingField &&
|
||||||
editingField !== getFieldName(transaction.id, 'category')) ||
|
editingField !==
|
||||||
|
getFieldName(serializedTransaction.id, 'category')) ||
|
||||||
isOffBudget ||
|
isOffBudget ||
|
||||||
isBudgetTransfer(transaction)
|
isBudgetTransfer(serializedTransaction)
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
onEditFieldInner(serializedTransaction.id, 'category')
|
||||||
}
|
}
|
||||||
onClick={() => onEditFieldInner(transaction.id, 'category')}
|
|
||||||
data-testid="category-field"
|
data-testid="category-field"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{childTransactions.map((childTrans, i, arr) => (
|
{serializedChildTransactions.map((childTrans, i, arr) => (
|
||||||
<ChildTransactionEdit
|
<ChildTransactionEdit
|
||||||
key={childTrans.id}
|
key={childTrans.id}
|
||||||
transaction={childTrans}
|
transaction={childTrans}
|
||||||
@@ -874,7 +1074,8 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{transaction.amount !== 0 && childTransactions.length === 0 && (
|
{serializedTransaction.amount !== 0 &&
|
||||||
|
serializedChildTransactions.length === 0 && (
|
||||||
<View style={{ alignItems: 'center' }}>
|
<View style={{ alignItems: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
disabled={editingField}
|
disabled={editingField}
|
||||||
@@ -886,7 +1087,7 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
onClick={() => onSplit(transaction.id)}
|
onClick={() => onSplit(serializedTransaction.id)}
|
||||||
type="bare"
|
type="bare"
|
||||||
>
|
>
|
||||||
<SvgSplit
|
<SvgSplit
|
||||||
@@ -912,10 +1113,12 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
<TapField
|
<TapField
|
||||||
disabled={
|
disabled={
|
||||||
editingField &&
|
editingField &&
|
||||||
editingField !== getFieldName(transaction.id, 'account')
|
editingField !== getFieldName(serializedTransaction.id, 'account')
|
||||||
}
|
}
|
||||||
value={account?.name}
|
value={account?.name}
|
||||||
onClick={() => onEditFieldInner(transaction.id, 'account')}
|
onClick={() =>
|
||||||
|
onEditFieldInner(serializedTransaction.id, 'account')
|
||||||
|
}
|
||||||
data-testid="account-field"
|
data-testid="account-field"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -927,24 +1130,26 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
type="date"
|
type="date"
|
||||||
disabled={
|
disabled={
|
||||||
editingField &&
|
editingField &&
|
||||||
editingField !== getFieldName(transaction.id, 'date')
|
editingField !== getFieldName(serializedTransaction.id, 'date')
|
||||||
}
|
}
|
||||||
required
|
required
|
||||||
style={{ color: theme.tableText, minWidth: '150px' }}
|
style={{ color: theme.tableText, minWidth: '150px' }}
|
||||||
defaultValue={dateDefaultValue}
|
defaultValue={dateDefaultValue}
|
||||||
onFocus={() =>
|
onFocus={() =>
|
||||||
onRequestActiveEdit(getFieldName(transaction.id, 'date'))
|
onRequestActiveEdit(
|
||||||
|
getFieldName(serializedTransaction.id, 'date'),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onUpdate={value =>
|
onUpdate={value =>
|
||||||
onUpdateInner(
|
onUpdateInner(
|
||||||
transaction,
|
serializedTransaction,
|
||||||
'date',
|
'date',
|
||||||
formatDate(parseISO(value), dateFormat),
|
formatDate(parseISO(value), dateFormat),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{transaction.reconciled ? (
|
{serializedTransaction.reconciled ? (
|
||||||
<View style={{ alignItems: 'center' }}>
|
<View style={{ alignItems: 'center' }}>
|
||||||
<FieldLabel title={t('Reconciled')} />
|
<FieldLabel title={t('Reconciled')} />
|
||||||
<Toggle id="Reconciled" isOn isDisabled />
|
<Toggle id="Reconciled" isOn isDisabled />
|
||||||
@@ -954,8 +1159,10 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
<FieldLabel title={t('Cleared')} />
|
<FieldLabel title={t('Cleared')} />
|
||||||
<ToggleField
|
<ToggleField
|
||||||
id="cleared"
|
id="cleared"
|
||||||
isOn={transaction.cleared}
|
isOn={serializedTransaction.cleared}
|
||||||
onToggle={on => onUpdateInner(transaction, 'cleared', on)}
|
onToggle={on =>
|
||||||
|
onUpdateInner(serializedTransaction, 'cleared', on)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -966,20 +1173,24 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
<InputField
|
<InputField
|
||||||
disabled={
|
disabled={
|
||||||
editingField &&
|
editingField &&
|
||||||
editingField !== getFieldName(transaction.id, 'notes')
|
editingField !== getFieldName(serializedTransaction.id, 'notes')
|
||||||
}
|
}
|
||||||
defaultValue={transaction.notes}
|
defaultValue={serializedTransaction.notes}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
onRequestActiveEdit(getFieldName(transaction.id, 'notes'));
|
onRequestActiveEdit(
|
||||||
|
getFieldName(serializedTransaction.id, 'notes'),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
onUpdate={value => onUpdateInner(transaction, 'notes', value)}
|
onUpdate={value =>
|
||||||
|
onUpdateInner(serializedTransaction, 'notes', value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{!adding && (
|
{!isAddingRef.current && (
|
||||||
<View style={{ alignItems: 'center' }}>
|
<View style={{ alignItems: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onDeleteInner(transaction.id)}
|
onClick={() => onDeleteInner(serializedTransaction.id)}
|
||||||
style={{
|
style={{
|
||||||
height: 40,
|
height: 40,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
@@ -1010,273 +1221,12 @@ const TransactionEditInner = memo(function TransactionEditInner({
|
|||||||
</View>
|
</View>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
function isTemporary(transaction) {
|
|
||||||
return transaction.id.indexOf('temp') === 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTemporaryTransactions(accountId, categoryId, lastDate) {
|
export function TransactionEdit() {
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'temp',
|
|
||||||
date: lastDate || monthUtils.currentDay(),
|
|
||||||
account: accountId,
|
|
||||||
category: categoryId,
|
|
||||||
amount: 0,
|
|
||||||
cleared: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function TransactionEditUnconnected({
|
|
||||||
categories,
|
|
||||||
accounts,
|
|
||||||
payees,
|
|
||||||
lastTransaction,
|
|
||||||
dateFormat,
|
|
||||||
}) {
|
|
||||||
const { transactionId } = useParams();
|
|
||||||
const { state: locationState } = useLocation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const [transactions, setTransactions] = useState([]);
|
|
||||||
const [fetchedTransactions, setFetchedTransactions] = useState([]);
|
|
||||||
const adding = useRef(false);
|
|
||||||
const deleted = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let unmounted = false;
|
|
||||||
|
|
||||||
async function fetchTransaction() {
|
|
||||||
// Query for the transaction based on the ID with grouped splits.
|
|
||||||
//
|
|
||||||
// This means if the transaction in question is a split transaction, its
|
|
||||||
// subtransactions will be returned in the `substransactions` property on
|
|
||||||
// the parent transaction.
|
|
||||||
//
|
|
||||||
// The edit item components expect to work with a flat array of
|
|
||||||
// transactions when handling splits, so we call ungroupTransactions to
|
|
||||||
// flatten parent and children into one array.
|
|
||||||
const { data } = await runQuery(
|
|
||||||
q('transactions')
|
|
||||||
.filter({ id: transactionId })
|
|
||||||
.select('*')
|
|
||||||
.options({ splits: 'grouped' }),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!unmounted) {
|
|
||||||
const fetchedTransactions = ungroupTransactions(data);
|
|
||||||
setTransactions(fetchedTransactions);
|
|
||||||
setFetchedTransactions(fetchedTransactions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (transactionId !== 'new') {
|
|
||||||
fetchTransaction();
|
|
||||||
} else {
|
|
||||||
adding.current = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unmounted = true;
|
|
||||||
};
|
|
||||||
}, [transactionId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (adding.current) {
|
|
||||||
setTransactions(
|
|
||||||
makeTemporaryTransactions(
|
|
||||||
locationState?.accountId || lastTransaction?.account || null,
|
|
||||||
locationState?.categoryId || null,
|
|
||||||
lastTransaction?.date,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [locationState?.accountId, locationState?.categoryId, lastTransaction]);
|
|
||||||
|
|
||||||
const onUpdate = useCallback(
|
|
||||||
async (serializedTransaction, updatedField) => {
|
|
||||||
const transaction = deserializeTransaction(
|
|
||||||
serializedTransaction,
|
|
||||||
null,
|
|
||||||
dateFormat,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Run the rules to auto-fill in any data. Right now we only do
|
|
||||||
// this on new transactions because that's how desktop works.
|
|
||||||
const newTransaction = { ...transaction };
|
|
||||||
if (isTemporary(newTransaction)) {
|
|
||||||
const afterRules = await send('rules-run', {
|
|
||||||
transaction: newTransaction,
|
|
||||||
});
|
|
||||||
const diff = getChangedValues(newTransaction, afterRules);
|
|
||||||
|
|
||||||
if (diff) {
|
|
||||||
Object.keys(diff).forEach(field => {
|
|
||||||
if (
|
|
||||||
newTransaction[field] == null ||
|
|
||||||
newTransaction[field] === '' ||
|
|
||||||
newTransaction[field] === 0 ||
|
|
||||||
newTransaction[field] === false
|
|
||||||
) {
|
|
||||||
newTransaction[field] = diff[field];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// When a rule updates a parent transaction, overwrite all changes to the current field in subtransactions.
|
|
||||||
if (
|
|
||||||
newTransaction.is_parent &&
|
|
||||||
diff.subtransactions !== undefined &&
|
|
||||||
updatedField !== null
|
|
||||||
) {
|
|
||||||
newTransaction.subtransactions = diff.subtransactions.map(
|
|
||||||
(st, idx) => ({
|
|
||||||
...(newTransaction.subtransactions[idx] || st),
|
|
||||||
...(st[updatedField] != null && {
|
|
||||||
[updatedField]: st[updatedField],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: newTransactions } = updateTransaction(
|
|
||||||
transactions,
|
|
||||||
newTransaction,
|
|
||||||
);
|
|
||||||
setTransactions(newTransactions);
|
|
||||||
},
|
|
||||||
[dateFormat, transactions],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSave = useCallback(
|
|
||||||
async newTransactions => {
|
|
||||||
if (deleted.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const changes = diffItems(fetchedTransactions || [], newTransactions);
|
|
||||||
if (
|
|
||||||
changes.added.length > 0 ||
|
|
||||||
changes.updated.length > 0 ||
|
|
||||||
changes.deleted.length
|
|
||||||
) {
|
|
||||||
const _remoteUpdates = await send('transactions-batch-update', {
|
|
||||||
added: changes.added,
|
|
||||||
deleted: changes.deleted,
|
|
||||||
updated: changes.updated,
|
|
||||||
});
|
|
||||||
|
|
||||||
// if (onTransactionsChange) {
|
|
||||||
// onTransactionsChange({
|
|
||||||
// ...changes,
|
|
||||||
// updated: changes.updated.concat(remoteUpdates),
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adding.current) {
|
|
||||||
// The first one is always the "parent" and the only one we care
|
|
||||||
// about
|
|
||||||
dispatch(setLastTransaction(newTransactions[0]));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[dispatch, fetchedTransactions],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDelete = useCallback(
|
|
||||||
async id => {
|
|
||||||
const changes = deleteTransaction(transactions, id);
|
|
||||||
|
|
||||||
if (adding.current) {
|
|
||||||
// Adding a new transactions, this disables saving when the component unmounts
|
|
||||||
deleted.current = true;
|
|
||||||
} else {
|
|
||||||
const _remoteUpdates = await send('transactions-batch-update', {
|
|
||||||
deleted: changes.diff.deleted,
|
|
||||||
});
|
|
||||||
|
|
||||||
// if (onTransactionsChange) {
|
|
||||||
// onTransactionsChange({ ...changes, updated: remoteUpdates });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
setTransactions(changes.data);
|
|
||||||
},
|
|
||||||
[transactions],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onAddSplit = useCallback(
|
|
||||||
id => {
|
|
||||||
const changes = addSplitTransaction(transactions, id);
|
|
||||||
setTransactions(changes.data);
|
|
||||||
},
|
|
||||||
[transactions],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSplit = useCallback(
|
|
||||||
id => {
|
|
||||||
const changes = splitTransaction(transactions, id, parent => [
|
|
||||||
makeChild(parent),
|
|
||||||
makeChild(parent),
|
|
||||||
]);
|
|
||||||
|
|
||||||
setTransactions(changes.data);
|
|
||||||
},
|
|
||||||
[transactions],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
categories.length === 0 ||
|
|
||||||
accounts.length === 0 ||
|
|
||||||
transactions.length === 0
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: theme.pageBackground,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TransactionEditInner
|
|
||||||
transactions={transactions}
|
|
||||||
adding={adding.current}
|
|
||||||
categories={categories}
|
|
||||||
accounts={accounts}
|
|
||||||
payees={payees}
|
|
||||||
navigate={navigate}
|
|
||||||
dateFormat={dateFormat}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
onSave={onSave}
|
|
||||||
onDelete={onDelete}
|
|
||||||
onSplit={onSplit}
|
|
||||||
onAddSplit={onAddSplit}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TransactionEdit = props => {
|
|
||||||
const { list: categories } = useCategories();
|
|
||||||
const payees = usePayees();
|
|
||||||
const lastTransaction = useSelector(state => state.queries.lastTransaction);
|
|
||||||
const accounts = useAccounts();
|
|
||||||
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleActiveEditFormProvider formName="mobile-transaction">
|
<SingleActiveEditFormProvider formName="mobile-transaction">
|
||||||
<TransactionEditUnconnected
|
<TransactionEditInner />
|
||||||
{...props}
|
|
||||||
categories={categories}
|
|
||||||
payees={payees}
|
|
||||||
lastTransaction={lastTransaction}
|
|
||||||
accounts={accounts}
|
|
||||||
dateFormat={dateFormat}
|
|
||||||
/>
|
|
||||||
</SingleActiveEditFormProvider>
|
</SingleActiveEditFormProvider>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user