From f715ceafc97a2de81536083b66fb833c8abc999d Mon Sep 17 00:00:00 2001
From: Matt Fiddaman <github@m.fiddaman.uk>
Date: Thu, 15 Aug 2024 14:40:20 +0100
Subject: [PATCH] hide target category from cover overspending list (#3115)

* hide target category from cover overspending list

* release note

* suggestions from joel-jeremy

* useMemo and better types

* add change to mobile

* fix build

* fix mobile
---
 .../desktop-client/src/components/Modals.tsx  |  1 +
 .../budget/rollover/BalanceMovementMenu.tsx   |  1 +
 .../components/budget/rollover/CoverMenu.tsx  | 40 +++++++++++++++----
 .../components/mobile/budget/BudgetTable.jsx  |  1 +
 .../src/components/modals/CoverModal.tsx      | 31 +++++++++++++-
 upcoming-release-notes/3115.md                |  6 +++
 6 files changed, 71 insertions(+), 9 deletions(-)
 create mode 100644 upcoming-release-notes/3115.md

diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx
index ea1381029..5a45d2159 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 3b8c24408..042dc81f5 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 b15a28252..1fec28b1e 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 a8782b83f..bfd5c5514 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 acdc38cb5..70ff82bd5 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 000000000..fd2b7fa1f
--- /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
-- 
GitLab