From 4e6a3bbace984158627b0ab67081cc31f5d8b08e Mon Sep 17 00:00:00 2001
From: Matiss Janis Aboltins <matiss@mja.lv>
Date: Tue, 17 Sep 2024 20:21:06 +0100
Subject: [PATCH] :recycle: (synced-prefs) move budget type to synced prefs
 (#3427)

---
 .../autocomplete/CategoryAutocomplete.tsx     |  4 +-
 .../src/components/budget/index.tsx           |  4 +-
 .../src/components/budget/util.ts             | 20 ++------
 .../components/mobile/budget/BudgetTable.jsx  |  6 +--
 .../src/components/mobile/budget/index.tsx    |  3 +-
 .../settings/BudgetTypeSettings.tsx           | 47 ++++---------------
 .../src/components/settings/Experimental.tsx  |  3 +-
 .../migrations/1723665565000_prefs.js         |  2 +-
 .../loot-core/src/server/budget/actions.ts    |  9 ++--
 packages/loot-core/src/server/main.ts         | 20 ++------
 packages/loot-core/src/server/prefs.ts        |  2 +-
 packages/loot-core/src/server/sheet.ts        |  6 ++-
 packages/loot-core/src/server/sync/index.ts   | 10 ++--
 packages/loot-core/src/types/prefs.d.ts       |  3 +-
 .../loot-core/src/types/server-handlers.d.ts  |  2 -
 upcoming-release-notes/3352.md                |  2 +-
 upcoming-release-notes/3427.md                |  6 +++
 17 files changed, 49 insertions(+), 100 deletions(-)
 create mode 100644 upcoming-release-notes/3427.md

diff --git a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx
index c8733e817..c5d0a2462 100644
--- a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx
+++ b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx
@@ -22,7 +22,7 @@ import {
 } from 'loot-core/src/types/models';
 
 import { useCategories } from '../../hooks/useCategories';
-import { useMetadataPref } from '../../hooks/useMetadataPref';
+import { useSyncedPref } from '../../hooks/useSyncedPref';
 import { SvgSplit } from '../../icons/v0';
 import { useResponsive } from '../../ResponsiveProvider';
 import { type CSSProperties, theme, styles } from '../../style';
@@ -379,7 +379,7 @@ function CategoryItem({
         borderTop: `1px solid ${theme.pillBorder}`,
       }
     : {};
-  const [budgetType = 'rollover'] = useMetadataPref('budgetType');
+  const [budgetType = 'rollover'] = useSyncedPref('budgetType');
 
   const balanceBinding =
     budgetType === 'rollover'
diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx
index e1ecb6e9b..af22d5b6e 100644
--- a/packages/desktop-client/src/components/budget/index.tsx
+++ b/packages/desktop-client/src/components/budget/index.tsx
@@ -24,8 +24,8 @@ import * as monthUtils from 'loot-core/src/shared/months';
 import { useCategories } from '../../hooks/useCategories';
 import { useGlobalPref } from '../../hooks/useGlobalPref';
 import { useLocalPref } from '../../hooks/useLocalPref';
-import { useMetadataPref } from '../../hooks/useMetadataPref';
 import { useNavigate } from '../../hooks/useNavigate';
+import { useSyncedPref } from '../../hooks/useSyncedPref';
 import { styles } from '../../style';
 import { View } from '../common/View';
 import { NamespaceContext } from '../spreadsheet/NamespaceContext';
@@ -78,7 +78,7 @@ function BudgetInner(props: BudgetInnerProps) {
     start: startMonth,
     end: startMonth,
   });
-  const [budgetType = 'rollover'] = useMetadataPref('budgetType');
+  const [budgetType = 'rollover'] = useSyncedPref('budgetType');
   const [maxMonthsPref] = useGlobalPref('maxMonths');
   const maxMonths = maxMonthsPref || 1;
   const [initialized, setInitialized] = useState(false);
diff --git a/packages/desktop-client/src/components/budget/util.ts b/packages/desktop-client/src/components/budget/util.ts
index 239a5ba15..c8f7c6185 100644
--- a/packages/desktop-client/src/components/budget/util.ts
+++ b/packages/desktop-client/src/components/budget/util.ts
@@ -4,7 +4,7 @@ import { send } from 'loot-core/src/platform/client/fetch';
 import * as monthUtils from 'loot-core/src/shared/months';
 import { type Handlers } from 'loot-core/src/types/handlers';
 import { type CategoryGroupEntity } from 'loot-core/src/types/models';
-import { type MetadataPrefs } from 'loot-core/src/types/prefs';
+import { type SyncedPrefs } from 'loot-core/src/types/prefs';
 
 import { type CSSProperties, styles, theme } from '../../style';
 import { type DropPosition } from '../sort';
@@ -141,7 +141,7 @@ export function getScrollbarWidth() {
 }
 
 export async function prewarmMonth(
-  budgetType: MetadataPrefs['budgetType'],
+  budgetType: SyncedPrefs['budgetType'],
   spreadsheet: ReturnType<typeof useSpreadsheet>,
   month: string,
 ) {
@@ -156,7 +156,7 @@ export async function prewarmMonth(
 }
 
 export async function prewarmAllMonths(
-  budgetType: MetadataPrefs['budgetType'],
+  budgetType: SyncedPrefs['budgetType'],
   spreadsheet: ReturnType<typeof useSpreadsheet>,
   bounds: { start: string; end: string },
   startMonth: string,
@@ -174,17 +174,3 @@ export async function prewarmAllMonths(
     months.map(month => prewarmMonth(budgetType, spreadsheet, month)),
   );
 }
-
-export async function switchBudgetType(
-  newBudgetType: MetadataPrefs['budgetType'],
-  spreadsheet: ReturnType<typeof useSpreadsheet>,
-  bounds: { start: string; end: string },
-  startMonth: string,
-  onSuccess: () => Promise<void> | undefined,
-) {
-  spreadsheet.disableObservers();
-  await send('budget-set-type', { type: newBudgetType });
-  await prewarmAllMonths(newBudgetType, spreadsheet, bounds, startMonth);
-  spreadsheet.enableObservers();
-  await onSuccess?.();
-}
diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx
index bcef3cfa2..5d2e1c892 100644
--- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx
+++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx
@@ -12,9 +12,9 @@ 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 { useMetadataPref } from '../../../hooks/useMetadataPref';
 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';
@@ -225,7 +225,7 @@ function BudgetCell({
 }) {
   const dispatch = useDispatch();
   const { showUndoNotification } = useUndo();
-  const [budgetType = 'rollover'] = useMetadataPref('budgetType');
+  const [budgetType = 'rollover'] = useSyncedPref('budgetType');
 
   const categoryBudgetMenuModal = `${budgetType}-budget-menu`;
   const categoryNotes = useNotes(category.id);
@@ -358,7 +358,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
   const goalTemp = useSheetValue(goal);
   const goalValue = isGoalTemplatesEnabled ? goalTemp : null;
 
-  const [budgetType = 'rollover'] = useMetadataPref('budgetType');
+  const [budgetType = 'rollover'] = useSyncedPref('budgetType');
   const dispatch = useDispatch();
   const { showUndoNotification } = useUndo();
   const { list: categories } = useCategories();
diff --git a/packages/desktop-client/src/components/mobile/budget/index.tsx b/packages/desktop-client/src/components/mobile/budget/index.tsx
index cb8970708..ebb606238 100644
--- a/packages/desktop-client/src/components/mobile/budget/index.tsx
+++ b/packages/desktop-client/src/components/mobile/budget/index.tsx
@@ -23,7 +23,6 @@ import * as monthUtils from 'loot-core/src/shared/months';
 
 import { useCategories } from '../../../hooks/useCategories';
 import { useLocalPref } from '../../../hooks/useLocalPref';
-import { useMetadataPref } from '../../../hooks/useMetadataPref';
 import { useSetThemeColor } from '../../../hooks/useSetThemeColor';
 import { useSyncedPref } from '../../../hooks/useSyncedPref';
 import { AnimatedLoading } from '../../../icons/AnimatedLoading';
@@ -43,7 +42,7 @@ export function Budget() {
   useSetThemeColor(theme.mobileViewTheme);
 
   const { list: categories, grouped: categoryGroups } = useCategories();
-  const [budgetTypePref] = useMetadataPref('budgetType');
+  const [budgetTypePref] = useSyncedPref('budgetType');
   const budgetType = isBudgetType(budgetTypePref) ? budgetTypePref : 'rollover';
   const spreadsheet = useSpreadsheet();
 
diff --git a/packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx b/packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx
index 7bc50ec01..c66463cec 100644
--- a/packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx
+++ b/packages/desktop-client/src/components/settings/BudgetTypeSettings.tsx
@@ -1,58 +1,27 @@
-import React, { useState } from 'react';
-import { useDispatch } from 'react-redux';
+import React from 'react';
 
-import { loadPrefs } from 'loot-core/src/client/actions';
-import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
-import * as monthUtils from 'loot-core/src/shared/months';
-
-import { useLocalPref } from '../../hooks/useLocalPref';
-import { useMetadataPref } from '../../hooks/useMetadataPref';
-import { switchBudgetType } from '../budget/util';
-import { ButtonWithLoading } from '../common/Button2';
+import { useSyncedPref } from '../../hooks/useSyncedPref';
+import { Button } from '../common/Button2';
 import { Link } from '../common/Link';
 import { Text } from '../common/Text';
 
 import { Setting } from './UI';
 
 export function BudgetTypeSettings() {
-  const dispatch = useDispatch();
-  const [budgetType = 'rollover'] = useMetadataPref('budgetType');
-  const [loading, setLoading] = useState(false);
-
-  const currentMonth = monthUtils.currentMonth();
-  const [startMonthPref] = useLocalPref('budget.startMonth');
-  const startMonth = startMonthPref || currentMonth;
-  const spreadsheet = useSpreadsheet();
+  const [budgetType = 'rollover', setBudgetType] = useSyncedPref('budgetType');
 
   function onSwitchType() {
-    setLoading(true);
-
-    if (!loading) {
-      const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
-
-      switchBudgetType(
-        newBudgetType,
-        spreadsheet,
-        {
-          start: startMonth,
-          end: startMonth,
-        },
-        startMonth,
-        async () => {
-          dispatch(loadPrefs());
-          setLoading(false);
-        },
-      );
-    }
+    const newBudgetType = budgetType === 'rollover' ? 'report' : 'rollover';
+    setBudgetType(newBudgetType);
   }
 
   return (
     <Setting
       primaryAction={
-        <ButtonWithLoading isLoading={loading} onPress={onSwitchType}>
+        <Button onPress={onSwitchType}>
           Switch to {budgetType === 'report' ? 'envelope' : 'tracking'}{' '}
           budgeting
-        </ButtonWithLoading>
+        </Button>
       }
     >
       <Text>
diff --git a/packages/desktop-client/src/components/settings/Experimental.tsx b/packages/desktop-client/src/components/settings/Experimental.tsx
index 58ccc41d7..fa6da21cf 100644
--- a/packages/desktop-client/src/components/settings/Experimental.tsx
+++ b/packages/desktop-client/src/components/settings/Experimental.tsx
@@ -4,7 +4,6 @@ import { Trans, useTranslation } from 'react-i18next';
 import type { FeatureFlag } from 'loot-core/src/types/prefs';
 
 import { useFeatureFlag } from '../../hooks/useFeatureFlag';
-import { useMetadataPref } from '../../hooks/useMetadataPref';
 import { useSyncedPref } from '../../hooks/useSyncedPref';
 import { theme } from '../../style';
 import { Link } from '../common/Link';
@@ -70,7 +69,7 @@ function FeatureToggle({
 
 function ReportBudgetFeature() {
   const { t } = useTranslation();
-  const [budgetType = 'rollover'] = useMetadataPref('budgetType');
+  const [budgetType = 'rollover'] = useSyncedPref('budgetType');
   const enabled = useFeatureFlag('reportBudget');
   const blockToggleOff = budgetType === 'report' && enabled;
   return (
diff --git a/packages/loot-core/migrations/1723665565000_prefs.js b/packages/loot-core/migrations/1723665565000_prefs.js
index cd52e78a2..fa0ef1e32 100644
--- a/packages/loot-core/migrations/1723665565000_prefs.js
+++ b/packages/loot-core/migrations/1723665565000_prefs.js
@@ -12,7 +12,7 @@ const SYNCED_PREF_KEYS = [
   /^csv-has-header-/,
   /^ofx-fallback-missing-payee-/,
   /^flip-amount-/,
-  // 'budgetType', // TODO: uncomment when `budgetType` moves from metadata to synced prefs
+  'budgetType',
   /^flags\./,
 ];
 
diff --git a/packages/loot-core/src/server/budget/actions.ts b/packages/loot-core/src/server/budget/actions.ts
index 88ef2f47f..28b803e72 100644
--- a/packages/loot-core/src/server/budget/actions.ts
+++ b/packages/loot-core/src/server/budget/actions.ts
@@ -2,7 +2,6 @@
 import * as monthUtils from '../../shared/months';
 import { safeNumber } from '../../shared/util';
 import * as db from '../db';
-import * as prefs from '../prefs';
 import * as sheet from '../sheet';
 import { batchMessages } from '../sync';
 
@@ -28,12 +27,14 @@ function calcBufferedAmount(
 }
 
 function getBudgetTable(): string {
-  const { budgetType } = prefs.getPrefs() || {};
-  return budgetType === 'report' ? 'reflect_budgets' : 'zero_budgets';
+  return isReflectBudget() ? 'reflect_budgets' : 'zero_budgets';
 }
 
 export function isReflectBudget(): boolean {
-  const { budgetType } = prefs.getPrefs();
+  const budgetType =
+    db.firstSync(`SELECT value FROM preferences WHERE id = ?`, [
+      'budgetType',
+    ]) ?? 'rollover';
   return budgetType === 'report';
 }
 
diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts
index f3b84fa9a..96dcd8552 100644
--- a/packages/loot-core/src/server/main.ts
+++ b/packages/loot-core/src/server/main.ts
@@ -268,20 +268,6 @@ handlers['report-budget-month'] = async function ({ month }) {
   return values;
 };
 
-handlers['budget-set-type'] = async function ({ type }) {
-  if (!prefs.BUDGET_TYPES.includes(type)) {
-    throw new Error('Invalid budget type: ' + type);
-  }
-
-  // It's already the same; don't do anything
-  if (type === prefs.getPrefs().budgetType) {
-    return;
-  }
-
-  // Save prefs
-  return prefs.savePrefs({ budgetType: type });
-};
-
 handlers['category-create'] = mutator(async function ({
   name,
   groupId,
@@ -1991,7 +1977,11 @@ async function loadBudget(id) {
   }
 
   // This is a bit leaky, but we need to set the initial budget type
-  sheet.get().meta().budgetType = prefs.getPrefs().budgetType;
+  const { value: budgetType = 'rollover' } =
+    (await db.first('SELECT value from preferences WHERE id = ?', [
+      'budgetType',
+    ])) ?? {};
+  sheet.get().meta().budgetType = budgetType;
   await budget.createAllBudgets();
 
   // Load all the in-memory state
diff --git a/packages/loot-core/src/server/prefs.ts b/packages/loot-core/src/server/prefs.ts
index b353acfbe..14fd72ddc 100644
--- a/packages/loot-core/src/server/prefs.ts
+++ b/packages/loot-core/src/server/prefs.ts
@@ -44,7 +44,7 @@ export async function savePrefs(
     // Sync whitelisted prefs
     const messages: Message[] = Object.keys(prefsToSet)
       .map(key => {
-        if (key === 'budgetType' || key === 'budgetName') {
+        if (key === 'budgetName') {
           return {
             dataset: 'prefs',
             row: key,
diff --git a/packages/loot-core/src/server/sheet.ts b/packages/loot-core/src/server/sheet.ts
index cad83f701..b0729ba5c 100644
--- a/packages/loot-core/src/server/sheet.ts
+++ b/packages/loot-core/src/server/sheet.ts
@@ -6,7 +6,6 @@ import * as sqlite from '../platform/server/sqlite';
 import { sheetForMonth } from '../shared/months';
 
 import * as Platform from './platform';
-import * as prefs from './prefs';
 import { Spreadsheet } from './spreadsheet/spreadsheet';
 import { resolveName } from './spreadsheet/util';
 
@@ -196,7 +195,10 @@ export async function loadUserBudgets(db): Promise<void> {
   // TODO: Clear out the cache here so make sure future loads of the app
   // don't load any extra values that aren't set here
 
-  const { budgetType } = prefs.getPrefs() || {};
+  const { value: budgetType = 'rollover' } =
+    (await db.first('SELECT value from preferences WHERE id = ?', [
+      'budgetType',
+    ])) ?? {};
 
   const table = budgetType === 'report' ? 'reflect_budgets' : 'zero_budgets';
   const budgets = await db.all(`
diff --git a/packages/loot-core/src/server/sync/index.ts b/packages/loot-core/src/server/sync/index.ts
index 843da6e44..e50a1735c 100644
--- a/packages/loot-core/src/server/sync/index.ts
+++ b/packages/loot-core/src/server/sync/index.ts
@@ -359,6 +359,11 @@ export const applyMessages = sequential(async (messages: Message[]) => {
 
         currentMerkle = merkle.insert(currentMerkle, timestamp);
       }
+
+      // Special treatment for some synced prefs
+      if (dataset === 'preferences' && row === 'budgetType') {
+        setBudgetType(value);
+      }
     }
 
     if (checkSyncingMode('enabled')) {
@@ -384,11 +389,6 @@ export const applyMessages = sequential(async (messages: Message[]) => {
   // Save any synced prefs
   if (Object.keys(prefsToSet).length > 0) {
     prefs.savePrefs(prefsToSet, { avoidSync: true });
-
-    if (prefsToSet.budgetType) {
-      setBudgetType(prefsToSet.budgetType);
-    }
-
     connection.send('prefs-updated');
   }
 
diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts
index eb0700a57..311482687 100644
--- a/packages/loot-core/src/types/prefs.d.ts
+++ b/packages/loot-core/src/types/prefs.d.ts
@@ -12,6 +12,7 @@ export type FeatureFlag =
  */
 export type SyncedPrefs = Partial<
   Record<
+    | 'budgetType'
     | 'firstDayOfWeekIdx'
     | 'dateFormat'
     | 'numberFormat'
@@ -39,8 +40,6 @@ export type SyncedPrefs = Partial<
  * core database.
  */
 export type MetadataPrefs = Partial<{
-  // TODO: move budgetType to SyncedPrefs
-  budgetType: string;
   budgetName: string;
   id: string;
   lastUploaded: string;
diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts
index 51967882f..5a5c589f6 100644
--- a/packages/loot-core/src/types/server-handlers.d.ts
+++ b/packages/loot-core/src/types/server-handlers.d.ts
@@ -73,8 +73,6 @@ export interface ServerHandlers {
     }[]
   >;
 
-  'budget-set-type': (arg: { type }) => Promise<unknown>;
-
   'category-create': (arg: {
     name;
     groupId;
diff --git a/upcoming-release-notes/3352.md b/upcoming-release-notes/3352.md
index 72bc482ec..c99e63939 100644
--- a/upcoming-release-notes/3352.md
+++ b/upcoming-release-notes/3352.md
@@ -3,4 +3,4 @@ category: Maintenance
 authors: [nmathey]
 ---
 
-Support translations in Translation support for desktop-client/src/components/budget/BalanceWithCarryover.tsx
\ No newline at end of file
+Support translations in Translation support for desktop-client/src/components/budget/BalanceWithCarryover.tsx
diff --git a/upcoming-release-notes/3427.md b/upcoming-release-notes/3427.md
new file mode 100644
index 000000000..9624e7a03
--- /dev/null
+++ b/upcoming-release-notes/3427.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [MatissJanis]
+---
+
+SyncedPrefs: move budget type to synced preferences.
-- 
GitLab