diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 5a45d2159654f9a76d377f651401a4f9363f3612..351abf0cc89003940662f8ef6d144f7fa22168a6 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -87,7 +87,7 @@ export function Modals() { .map(({ name, options }) => { switch (name) { case 'keyboard-shortcuts': - return <KeyboardShortcutModal />; + return <KeyboardShortcutModal key={name} />; case 'import-transactions': return <ImportTransactions key={name} options={options} />; diff --git a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx index fa77bc00260f744a937cf014ac4d7834e0cc1823..d6ae0650bc21ef4ae15da84cd8d82991c1c6b3d6 100644 --- a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx +++ b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx @@ -7,6 +7,7 @@ import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; import * as monthUtils from 'loot-core/src/shared/months'; import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; +import { useUndo } from '../../../hooks/useUndo'; import { SvgCheveronDown } from '../../../icons/v1'; import { styles, theme, type CSSProperties } from '../../../style'; import { Button } from '../../common/Button2'; @@ -215,6 +216,8 @@ export const CategoryMonth = memo(function CategoryMonth({ setMenuOpen(false); }; + const { showUndoNotification } = useUndo(); + return ( <View style={{ @@ -280,6 +283,9 @@ export const CategoryMonth = memo(function CategoryMonth({ onMenuAction(month, 'copy-single-last', { category: category.id, }); + showUndoNotification({ + message: `Budget set to last month’s budget.`, + }); }} onSetMonthsAverage={numberOfMonths => { if ( @@ -293,11 +299,17 @@ export const CategoryMonth = memo(function CategoryMonth({ onMenuAction(month, `set-single-${numberOfMonths}-avg`, { category: category.id, }); + showUndoNotification({ + message: `Budget set to ${numberOfMonths}-month average.`, + }); }} onApplyBudgetTemplate={() => { onMenuAction(month, 'apply-single-category-template', { category: category.id, }); + showUndoNotification({ + message: `Budget template applied.`, + }); }} /> </Popover> diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx index 86c03d2c7871a64715e4c5cb5c7a7fb97c98adcf..ab107196717983dc0719a8b435481b7536db0558 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx @@ -5,6 +5,7 @@ import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; import * as monthUtils from 'loot-core/src/shared/months'; import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; +import { useUndo } from '../../../hooks/useUndo'; import { SvgCheveronDown } from '../../../icons/v1'; import { styles, theme, type CSSProperties } from '../../../style'; import { Button } from '../../common/Button2'; @@ -202,6 +203,8 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ setBudgetMenuOpen(false); }; + const { showUndoNotification } = useUndo(); + return ( <View style={{ @@ -268,6 +271,9 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ onMenuAction(month, 'copy-single-last', { category: category.id, }); + showUndoNotification({ + message: `Budget set to last month’s budget.`, + }); }} onSetMonthsAverage={numberOfMonths => { if ( @@ -281,11 +287,17 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ onMenuAction(month, `set-single-${numberOfMonths}-avg`, { category: category.id, }); + showUndoNotification({ + message: `Budget set to ${numberOfMonths}-month average.`, + }); }} onApplyBudgetTemplate={() => { onMenuAction(month, 'apply-single-category-template', { category: category.id, }); + showUndoNotification({ + message: `Budget template applied.`, + }); }} /> </Popover> diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index ce8d398e1c55699dd0cdf7de5a3e3ac827b98749..493e2635e527f852c4e3d7074de0e67150803385 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -5,13 +5,17 @@ import { AutoTextSize } from 'auto-text-size'; import memoizeOne from 'memoize-one'; import { collapseModals, pushModal } from 'loot-core/client/actions'; +import { groupById, integerToCurrency } from 'loot-core/shared/util'; import { rolloverBudget, reportBudget } from 'loot-core/src/client/queries'; import * as monthUtils from 'loot-core/src/shared/months'; +import { useCategories } from '../../../hooks/useCategories'; import { useFeatureFlag } from '../../../hooks/useFeatureFlag'; import { useLocalPref } from '../../../hooks/useLocalPref'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useNotes } from '../../../hooks/useNotes'; import { useSyncedPref } from '../../../hooks/useSyncedPref'; +import { useUndo } from '../../../hooks/useUndo'; import { SvgLogo } from '../../../icons/logo'; import { SvgExpandArrow } from '../../../icons/v0'; import { @@ -214,30 +218,35 @@ function BudgetCell({ name, binding, style, - categoryId, + category, month, onBudgetAction, ...props }) { const dispatch = useDispatch(); - const [budgetType = 'rollover'] = useSyncedPref('budgetType'); + const { showUndoNotification } = useUndo(); + const [budgetType = 'rollover'] = useLocalPref('budgetType'); const categoryBudgetMenuModal = `${budgetType}-budget-menu`; + const categoryNotes = useNotes(category.id); const onOpenCategoryBudgetMenu = () => { dispatch( pushModal(categoryBudgetMenuModal, { - categoryId, + categoryId: category.id, month, onUpdateBudget: amount => { onBudgetAction(month, 'budget-amount', { - category: categoryId, + category: category.id, amount, }); }, onCopyLastMonthAverage: () => { onBudgetAction(month, 'copy-single-last', { - category: categoryId, + category: category.id, + }); + showUndoNotification({ + message: `${category.name} budget has been set last to month’s budgeted amount.`, }); }, onSetMonthsAverage: numberOfMonths => { @@ -250,12 +259,19 @@ function BudgetCell({ } onBudgetAction(month, `set-single-${numberOfMonths}-avg`, { - category: categoryId, + category: category.id, + }); + showUndoNotification({ + message: `${category.name} budget has been set to ${numberOfMonths === 12 ? 'yearly' : `${numberOfMonths} month`} average.`, }); }, onApplyBudgetTemplate: () => { onBudgetAction(month, 'apply-single-category-template', { - category: categoryId, + category: category.id, + }); + showUndoNotification({ + message: `${category.name} budget templates have been applied.`, + pre: categoryNotes, }); }, }), @@ -344,6 +360,9 @@ const ExpenseCategory = memo(function ExpenseCategory({ const [budgetType = 'rollover'] = useSyncedPref('budgetType'); const dispatch = useDispatch(); + const { showUndoNotification } = useUndo(); + const { list: categories } = useCategories(); + const categoriesById = groupById(categories); const onCarryover = carryover => { onBudgetAction(month, 'carryover', { @@ -380,6 +399,9 @@ const ExpenseCategory = memo(function ExpenseCategory({ to: toCategoryId, }); dispatch(collapseModals(`${budgetType}-balance-menu`)); + showUndoNotification({ + message: `Transferred ${integerToCurrency(amount)} from ${category.name} to ${categoriesById[toCategoryId].name}.`, + }); }, showToBeBudgeted: true, }), @@ -398,6 +420,9 @@ const ExpenseCategory = memo(function ExpenseCategory({ from: fromCategoryId, }); dispatch(collapseModals(`${budgetType}-balance-menu`)); + showUndoNotification({ + message: `Covered ${category.name} overspending from ${categoriesById[fromCategoryId].name}.`, + }); }, }), ); @@ -495,7 +520,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ <BudgetCell name="budgeted" binding={budgeted} - categoryId={category.id} + category={category} month={month} onBudgetAction={onBudgetAction} formatter={value => ( @@ -1162,7 +1187,7 @@ const IncomeCategory = memo(function IncomeCategory({ <BudgetCell name="budgeted" binding={budgeted} - categoryId={category.id} + category={category} month={month} onBudgetAction={onBudgetAction} formatter={value => ( diff --git a/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx b/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx index 85c8100e9accd101a7e4af71ffab8a19a8ee9d8d..0d4693ad5e2f3697f0bf442d0f368dcd49e4ef9a 100644 --- a/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx @@ -76,7 +76,6 @@ export const Transaction = memo(function Transaction({ const payee = usePayee(payeeId); const account = useAccount(accountId); const transferAcct = useAccount(payee?.transfer_acct); - const isPreview = isPreviewId(id); const { longPressProps } = useLongPress({ diff --git a/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx b/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx index 4c996dfc0afd00704318e06a9b6ed9420e57454a..8ec998d0874dfbf7a1f34ef317c93e70bdeb55d0 100644 --- a/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBudgetMonthMenuModal.tsx @@ -4,6 +4,7 @@ import React, { useState } from 'react'; import * as monthUtils from 'loot-core/src/shared/months'; import { useNotes } from '../../hooks/useNotes'; +import { useUndo } from '../../hooks/useUndo'; import { SvgCheveronDown, SvgCheveronUp } from '../../icons/v1'; import { SvgNotesPaper } from '../../icons/v2'; import { type CSSProperties, styles, theme } from '../../style'; @@ -25,6 +26,7 @@ export function ReportBudgetMonthMenuModal({ onEditNotes, }: ReportBudgetMonthMenuModalProps) { const originalNotes = useNotes(`budget-${month}`); + const { showUndoNotification } = useUndo(); const _onEditNotes = () => { onEditNotes?.(month); @@ -51,6 +53,8 @@ export function ReportBudgetMonthMenuModal({ setShowMore(!showMore); }; + const displayMonth = monthUtils.format(month, 'MMMM ‘yy'); + return ( <Modal name="report-budget-month-menu" @@ -61,7 +65,7 @@ export function ReportBudgetMonthMenuModal({ {({ state: { close } }) => ( <> <ModalHeader - title={monthUtils.format(month, 'MMMM ‘yy')} + title={displayMonth} rightContent={<ModalCloseButton onClick={close} />} /> <View @@ -148,14 +152,23 @@ export function ReportBudgetMonthMenuModal({ onCopyLastMonthBudget={() => { onBudgetAction(month, 'copy-last'); close(); + showUndoNotification({ + message: `${displayMonth} budgets have all been set to last month’s budgeted amounts.`, + }); }} onSetBudgetsToZero={() => { onBudgetAction(month, 'set-zero'); close(); + showUndoNotification({ + message: `${displayMonth} budgets have all been set to zero.`, + }); }} onSetMonthsAverage={numberOfMonths => { onBudgetAction(month, `set-${numberOfMonths}-avg`); close(); + showUndoNotification({ + message: `${displayMonth} budgets have all been set to ${numberOfMonths === 12 ? 'yearly' : `${numberOfMonths} month`} average.`, + }); }} onCheckTemplates={() => { onBudgetAction(month, 'check-templates'); @@ -164,10 +177,16 @@ export function ReportBudgetMonthMenuModal({ onApplyBudgetTemplates={() => { onBudgetAction(month, 'apply-goal-template'); close(); + showUndoNotification({ + message: `${displayMonth} budget templates have been applied.`, + }); }} onOverwriteWithBudgetTemplates={() => { onBudgetAction(month, 'overwrite-goal-template'); close(); + showUndoNotification({ + message: `${displayMonth} budget templates have been overwritten.`, + }); }} /> )} diff --git a/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx index a34353e5dcb63f44cb6e22e0cb7ee67c0e5c457d..cb8aa0f5f135766f601c1ff9f76d59e895388c7b 100644 --- a/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBudgetMonthMenuModal.tsx @@ -4,6 +4,7 @@ import React, { useState } from 'react'; import * as monthUtils from 'loot-core/src/shared/months'; import { useNotes } from '../../hooks/useNotes'; +import { useUndo } from '../../hooks/useUndo'; import { SvgCheveronDown, SvgCheveronUp } from '../../icons/v1'; import { SvgNotesPaper } from '../../icons/v2'; import { type CSSProperties, styles, theme } from '../../style'; @@ -25,6 +26,7 @@ export function RolloverBudgetMonthMenuModal({ onEditNotes, }: RolloverBudgetMonthMenuModalProps) { const originalNotes = useNotes(`budget-${month}`); + const { showUndoNotification } = useUndo(); const _onEditNotes = () => { onEditNotes?.(month); @@ -51,6 +53,8 @@ export function RolloverBudgetMonthMenuModal({ setShowMore(!showMore); }; + const displayMonth = monthUtils.format(month, 'MMMM ‘yy'); + return ( <Modal name="rollover-budget-month-menu" @@ -61,7 +65,7 @@ export function RolloverBudgetMonthMenuModal({ {({ state: { close } }) => ( <> <ModalHeader - title={monthUtils.format(month, 'MMMM ‘yy')} + title={displayMonth} rightContent={<ModalCloseButton onClick={close} />} /> <View @@ -148,14 +152,23 @@ export function RolloverBudgetMonthMenuModal({ onCopyLastMonthBudget={() => { onBudgetAction(month, 'copy-last'); close(); + showUndoNotification({ + message: `${displayMonth} budgets have all been set to last month’s budgeted amounts.`, + }); }} onSetBudgetsToZero={() => { onBudgetAction(month, 'set-zero'); close(); + showUndoNotification({ + message: `${displayMonth} budgets have all been set to zero.`, + }); }} onSetMonthsAverage={numberOfMonths => { onBudgetAction(month, `set-${numberOfMonths}-avg`); close(); + showUndoNotification({ + message: `${displayMonth} budgets have all been set to ${numberOfMonths === 12 ? 'yearly' : `${numberOfMonths} month`} average.`, + }); }} onCheckTemplates={() => { onBudgetAction(month, 'check-templates'); @@ -164,14 +177,23 @@ export function RolloverBudgetMonthMenuModal({ onApplyBudgetTemplates={() => { onBudgetAction(month, 'apply-goal-template'); close(); + showUndoNotification({ + message: `${displayMonth} budget templates have been applied.`, + }); }} onOverwriteWithBudgetTemplates={() => { onBudgetAction(month, 'overwrite-goal-template'); close(); + showUndoNotification({ + message: `${displayMonth} budget templates have been overwritten.`, + }); }} onEndOfMonthCleanup={() => { onBudgetAction(month, 'cleanup-goal-template'); close(); + showUndoNotification({ + message: `${displayMonth} end-of-month cleanup templates have been applied.`, + }); }} /> )} diff --git a/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx b/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx index 0c64e62687afdce8e5a8af1248810e9e82a99c06..d86523af2197f5d1948734db27082e20c51deb32 100644 --- a/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBudgetSummaryModal.tsx @@ -3,8 +3,11 @@ import { useDispatch } from 'react-redux'; import { collapseModals, pushModal } from 'loot-core/client/actions'; import { rolloverBudget } from 'loot-core/client/queries'; +import { groupById, integerToCurrency } from 'loot-core/shared/util'; import { format, sheetForMonth, prevMonth } from 'loot-core/src/shared/months'; +import { useCategories } from '../../hooks/useCategories'; +import { useUndo } from '../../hooks/useUndo'; import { styles } from '../../style'; import { ToBudgetAmount } from '../budget/rollover/budgetsummary/ToBudgetAmount'; import { TotalsList } from '../budget/rollover/budgetsummary/TotalsList'; @@ -29,6 +32,10 @@ export function RolloverBudgetSummaryModal({ value: 0, }) ?? 0; + const { showUndoNotification } = useUndo(); + const { list: categories } = useCategories(); + const categoriesById = groupById(categories); + const openTransferAvailableModal = () => { dispatch( pushModal('transfer', { @@ -42,6 +49,9 @@ export function RolloverBudgetSummaryModal({ category: toCategoryId, }); dispatch(collapseModals('transfer')); + showUndoNotification({ + message: `Transferred ${integerToCurrency(amount)} to ${categoriesById[toCategoryId].name}`, + }); }, }), ); @@ -58,6 +68,9 @@ export function RolloverBudgetSummaryModal({ category: categoryId, }); dispatch(collapseModals('cover')); + showUndoNotification({ + message: `Covered overbudgeted from ${categoriesById[categoryId].name}`, + }); }, }), ); diff --git a/upcoming-release-notes/3085.md b/upcoming-release-notes/3085.md new file mode 100644 index 0000000000000000000000000000000000000000..48aa868428dbb70ac66084c7c5a019e22928df4c --- /dev/null +++ b/upcoming-release-notes/3085.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [joel-jeremy] +--- + +Show undo notifications when applying goal templates / budget actions in mobile.