From 29a515f3fe4eef198fea75dfe1856c8de49424ec Mon Sep 17 00:00:00 2001 From: xentara1 <30550215+xentara1@users.noreply.github.com> Date: Sat, 3 Feb 2024 19:00:27 +0000 Subject: [PATCH] [Feature] Add ability to create schedules from existing transactions (#2222) --- .../desktop-client/src/components/Modals.tsx | 3 ++ .../components/schedules/ScheduleDetails.jsx | 47 +++++++++++++++---- .../src/components/schedules/ScheduleLink.tsx | 30 ++++++++++++ .../transactions/SelectedTransactions.jsx | 2 + .../transactions/SimpleTransactionsTable.jsx | 10 ++-- upcoming-release-notes/2222.md | 6 +++ 6 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 upcoming-release-notes/2222.md diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 5974ec0e2..5163c587f 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -310,6 +310,7 @@ export function Modals() { modalProps={modalProps} id={options?.id || null} actions={actions} + transaction={options?.transaction || null} /> ); @@ -320,6 +321,8 @@ export function Modals() { modalProps={modalProps} actions={actions} transactionIds={options?.transactionIds} + getTransaction={options?.getTransaction} + pushModal={options?.pushModal} /> ); diff --git a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx index 8459e937c..85446055b 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx +++ b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx @@ -67,14 +67,14 @@ function updateScheduleConditions(schedule, fields) { }; } -export function ScheduleDetails({ modalProps, actions, id }) { +export function ScheduleDetails({ modalProps, actions, id, transaction }) { const adding = id == null; + const fromTrans = transaction != null; const payees = useCachedPayees({ idKey: true }); const globalDispatch = useDispatch(); const dateFormat = useSelector(state => { return state.prefs.local.dateFormat || 'MM/dd/yyyy'; }); - const [state, dispatch] = useReducer( (state, action) => { switch (action.type) { @@ -143,6 +143,11 @@ export function ScheduleDetails({ modalProps, actions, id }) { fields: { ...state.fields, ...fields }, }; case 'set-transactions': + if (fromTrans && action.transactions) { + action.transactions.sort(a => { + return transaction.id === a.id ? -1 : 1; + }); + } return { ...state, transactions: action.transactions }; case 'set-repeats': return { @@ -210,12 +215,30 @@ export function ScheduleDetails({ modalProps, actions, id }) { endOccurrences: '1', endDate: monthUtils.currentDay(), }; - const schedule = { - posts_transaction: false, - _date: date, - _conditions: [{ op: 'isapprox', field: 'date', value: date }], - _actions: [], - }; + + const schedule = fromTrans + ? { + posts_transaction: false, + _conditions: [{ op: 'isapprox', field: 'date', value: date }], + _actions: [], + _account: transaction.account, + _amount: transaction.amount, + _amountOp: 'is', + name: transaction.payee ? payees[transaction.payee].name : '', + _payee: transaction.payee ? transaction.payee : '', + _date: { + ...date, + frequency: 'monthly', + start: transaction.date, + patterns: [], + }, + } + : { + posts_transaction: false, + _date: date, + _conditions: [{ op: 'isapprox', field: 'date', value: date }], + _actions: [], + }; dispatch({ type: 'set-schedule', schedule }); } else { @@ -226,6 +249,7 @@ export function ScheduleDetails({ modalProps, actions, id }) { } } } + run(); }, []); @@ -321,7 +345,11 @@ export function ScheduleDetails({ modalProps, actions, id }) { }; }, [state.schedule, state.transactionsMode, state.fields]); - const selectedInst = useSelected('transactions', state.transactions, []); + const selectedInst = useSelected( + 'transactions', + state.transactions, + transaction ? [transaction.id] : [], + ); async function onSave() { dispatch({ type: 'form-error', error: null }); @@ -415,7 +443,6 @@ export function ScheduleDetails({ modalProps, actions, id }) { } const payee = payees ? payees[state.fields.payee] : null; - // This is derived from the date const repeats = state.fields.date ? !!state.fields.date.frequency : false; return ( diff --git a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx index c6251c12a..ed924fca2 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx +++ b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx @@ -4,8 +4,11 @@ import React, { useCallback, useRef, useState } from 'react'; import { useSchedules } from 'loot-core/src/client/data-hooks/schedules'; import { send } from 'loot-core/src/platform/client/fetch'; import { type Query } from 'loot-core/src/shared/query'; +import { type TransactionEntity } from 'loot-core/src/types/models'; import { type BoundActions } from '../../hooks/useActions'; +import { SvgAdd } from '../../icons/v0'; +import { Button } from '../common/Button'; import { Modal } from '../common/Modal'; import { Search } from '../common/Search'; import { Text } from '../common/Text'; @@ -14,14 +17,23 @@ import { type CommonModalProps } from '../Modals'; import { ROW_HEIGHT, SchedulesTable } from './SchedulesTable'; +type ModalParams = { + id: string; + transaction: TransactionEntity; +}; + export function ScheduleLink({ modalProps, actions, transactionIds: ids, + getTransaction, + pushModal, }: { actions: BoundActions; modalProps?: CommonModalProps; transactionIds: string[]; + getTransaction: (transactionId: string) => TransactionEntity; + pushModal: (name: string, params: ModalParams) => void; }) { const [filter, setFilter] = useState(''); @@ -45,6 +57,14 @@ export function ScheduleLink({ actions.popModal(); } + async function onCreate() { + actions.popModal(); + pushModal('schedule-edit', { + id: null, + transaction: getTransaction(ids[0]), + }); + } + return ( <Modal title="Link Schedule" size={{ width: 600 }} {...modalProps}> <View @@ -70,6 +90,16 @@ export function ScheduleLink({ value={filter} onChange={setFilter} /> + {ids.length === 1 && ( + <Button + type="primary" + style={{ marginLeft: 15, padding: '4px 10px' }} + onClick={onCreate} + > + <SvgAdd style={{ width: '20', padding: '3' }} /> + Create New + </Button> + )} </View> <View diff --git a/packages/desktop-client/src/components/transactions/SelectedTransactions.jsx b/packages/desktop-client/src/components/transactions/SelectedTransactions.jsx index d0c360982..fa4cffa4b 100644 --- a/packages/desktop-client/src/components/transactions/SelectedTransactions.jsx +++ b/packages/desktop-client/src/components/transactions/SelectedTransactions.jsx @@ -135,6 +135,8 @@ export function SelectedTransactionsButton({ case 'link-schedule': pushModal('schedule-link', { transactionIds: [...selectedItems], + getTransaction, + pushModal, }); break; case 'unlink-schedule': diff --git a/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx b/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx index 4d6f6610a..ea43f6adb 100644 --- a/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx @@ -1,10 +1,10 @@ -import React, { memo, useMemo, useCallback } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { format as formatDate, - parseISO, isValid as isDateValid, + parseISO, } from 'date-fns'; import { @@ -14,10 +14,10 @@ import { import { integerToCurrency } from 'loot-core/src/shared/util'; import { useCategories } from '../../hooks/useCategories'; -import { useSelectedItems, useSelectedDispatch } from '../../hooks/useSelected'; +import { useSelectedDispatch, useSelectedItems } from '../../hooks/useSelected'; import { SvgArrowsSynchronize } from '../../icons/v2'; -import { theme, styles } from '../../style'; -import { Table, Row, Field, Cell, SelectCell } from '../table'; +import { styles, theme } from '../../style'; +import { Cell, Field, Row, SelectCell, Table } from '../table'; import { DisplayId } from '../util/DisplayId'; function serializeTransaction(transaction, dateFormat) { diff --git a/upcoming-release-notes/2222.md b/upcoming-release-notes/2222.md new file mode 100644 index 000000000..1412f1fd8 --- /dev/null +++ b/upcoming-release-notes/2222.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [xentara1] +--- + +Add ability to create schedules from existing transactions -- GitLab