diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index ea138102900adf95c9f6621722ac8acef72dc8d0..5a45d2159654f9a76d377f651401a4f9363f3612 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -511,6 +511,7 @@ export function Modals() { title={options.title} month={options.month} showToBeBudgeted={options.showToBeBudgeted} + category={options.category} onSubmit={options.onSubmit} /> ); diff --git a/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx index 3b8c24408dd0c1d40202218b59df07ccdd340cec..042dc81f533371ddb4255e7a49d79af318f5ad02 100644 --- a/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx @@ -59,6 +59,7 @@ export function BalanceMovementMenu({ {menu === 'cover' && ( <CoverMenu + category={categoryId} onClose={onClose} onSubmit={fromCategoryId => { onBudgetAction(month, 'cover-overspending', { diff --git a/packages/desktop-client/src/components/budget/rollover/CoverMenu.tsx b/packages/desktop-client/src/components/budget/rollover/CoverMenu.tsx index b15a2825278336d66a3a94b860851866c47777f1..1fec28b1e7287256464e6968a8cd85575ac0cdc3 100644 --- a/packages/desktop-client/src/components/budget/rollover/CoverMenu.tsx +++ b/packages/desktop-client/src/components/budget/rollover/CoverMenu.tsx @@ -1,4 +1,9 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; + +import { + type CategoryGroupEntity, + type CategoryEntity, +} from 'loot-core/src/types/models'; import { useCategories } from '../../../hooks/useCategories'; import { CategoryAutocomplete } from '../../autocomplete/CategoryAutocomplete'; @@ -7,26 +12,47 @@ import { InitialFocus } from '../../common/InitialFocus'; import { View } from '../../common/View'; import { addToBeBudgetedGroup } from '../util'; +function removeSelectedCategory( + categoryGroups: CategoryGroupEntity[], + category?: CategoryEntity['id'], +) { + if (!category) return categoryGroups; + + return categoryGroups + .map(group => ({ + ...group, + categories: group.categories?.filter(cat => cat.id !== category), + })) + .filter(group => group.categories?.length); +} + type CoverMenuProps = { showToBeBudgeted?: boolean; + category?: CategoryEntity['id']; onSubmit: (categoryId: string) => void; onClose: () => void; }; export function CoverMenu({ showToBeBudgeted = true, + category, onSubmit, onClose, }: CoverMenuProps) { const { grouped: originalCategoryGroups } = useCategories(); - const filteredCategoryGroups = originalCategoryGroups.filter( - g => !g.is_income, - ); + const expenseGroups = originalCategoryGroups.filter(g => !g.is_income); + const categoryGroups = showToBeBudgeted - ? addToBeBudgetedGroup(filteredCategoryGroups) - : filteredCategoryGroups; + ? addToBeBudgetedGroup(expenseGroups) + : expenseGroups; + const [categoryId, setCategoryId] = useState<string | null>(null); + const filteredCategoryGroups = useMemo( + () => removeSelectedCategory(categoryGroups, category), + [categoryGroups, category], + ); + function submit() { if (categoryId) { onSubmit(categoryId); @@ -40,7 +66,7 @@ export function CoverMenu({ <InitialFocus> {node => ( <CategoryAutocomplete - categoryGroups={categoryGroups} + categoryGroups={filteredCategoryGroups} value={categoryGroups.find(g => g.id === categoryId) ?? null} openOnFocus={true} onSelect={(id: string | undefined) => setCategoryId(id || null)} diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index a8782b83fc4b3dbbb8b51274049ab9c1fcd5184b..bfd5c55146935872fe00eee326f4004331ffe329 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -391,6 +391,7 @@ const ExpenseCategory = memo(function ExpenseCategory({ pushModal('cover', { title: category.name, month, + category: category.id, onSubmit: fromCategoryId => { onBudgetAction(month, 'cover-overspending', { to: category.id, diff --git a/packages/desktop-client/src/components/modals/CoverModal.tsx b/packages/desktop-client/src/components/modals/CoverModal.tsx index acdc38cb51355c9e8f9d6833214cfbe6fd3b3c09..70ff82bd5a104d127adfa1b1a1e925c3c11dc6ce 100644 --- a/packages/desktop-client/src/components/modals/CoverModal.tsx +++ b/packages/desktop-client/src/components/modals/CoverModal.tsx @@ -2,6 +2,10 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; +import { + type CategoryGroupEntity, + type CategoryEntity, +} from 'loot-core/src/types/models'; import { useCategories } from '../../hooks/useCategories'; import { useInitialMount } from '../../hooks/useInitialMount'; @@ -12,10 +16,25 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal2'; import { View } from '../common/View'; import { FieldLabel, TapField } from '../mobile/MobileForms'; +function removeSelectedCategory( + categoryGroups: CategoryGroupEntity[], + category?: CategoryEntity['id'], +) { + if (!category) return categoryGroups; + + return categoryGroups + .map(group => ({ + ...group, + categories: group.categories?.filter(cat => cat.id !== category), + })) + .filter(group => group.categories?.length); +} + type CoverModalProps = { title: string; month: string; showToBeBudgeted?: boolean; + category?: CategoryEntity['id']; onSubmit: (categoryId: string) => void; }; @@ -23,6 +42,7 @@ export function CoverModal({ title, month, showToBeBudgeted = true, + category, onSubmit, }: CoverModalProps) { const { grouped: originalCategoryGroups } = useCategories(); @@ -30,27 +50,34 @@ export function CoverModal({ const filteredCategoryGroups = originalCategoryGroups.filter( g => !g.is_income, ); + const expenseGroups = showToBeBudgeted ? addToBeBudgetedGroup(filteredCategoryGroups) : filteredCategoryGroups; + const expenseCategories = expenseGroups.flatMap(g => g.categories || []); return [expenseGroups, expenseCategories]; }, [originalCategoryGroups, showToBeBudgeted]); + const filteredCategoryGroups = useMemo( + () => removeSelectedCategory(categoryGroups, category), + [categoryGroups, category], + ); + const [fromCategoryId, setFromCategoryId] = useState<string | null>(null); const dispatch = useDispatch(); const onCategoryClick = useCallback(() => { dispatch( pushModal('category-autocomplete', { - categoryGroups, + categoryGroups: filteredCategoryGroups, month, onSelect: categoryId => { setFromCategoryId(categoryId); }, }), ); - }, [categoryGroups, dispatch, month]); + }, [filteredCategoryGroups, dispatch, month]); const _onSubmit = (categoryId: string | null) => { if (categoryId) { diff --git a/upcoming-release-notes/3115.md b/upcoming-release-notes/3115.md new file mode 100644 index 0000000000000000000000000000000000000000..fd2b7fa1fd9c112b8eab9ebe505427b0cb1fcf78 --- /dev/null +++ b/upcoming-release-notes/3115.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [matt-fidd] +--- + +Hide the target category from the cover overspending category list