From 44770a36c7b71689bc8251cf752a8582f58a8868 Mon Sep 17 00:00:00 2001
From: Matiss Janis Aboltins <matiss@mja.lv>
Date: Mon, 13 May 2024 17:34:46 +0100
Subject: [PATCH] :bug: fix networth graph loading forever for empty budgets
 (#2725)

---
 packages/desktop-client/package.json          |  2 +
 .../spreadsheets/net-worth-spreadsheet.ts     | 67 +++++++++++++------
 .../spreadsheets/spending-spreadsheet.ts      |  5 +-
 .../src/components/reports/util.ts            | 12 ----
 upcoming-release-notes/2725.md                |  6 ++
 yarn.lock                                     |  9 +++
 6 files changed, 66 insertions(+), 35 deletions(-)
 create mode 100644 upcoming-release-notes/2725.md

diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json
index c1617dcb0..1c3f218ca 100644
--- a/packages/desktop-client/package.json
+++ b/packages/desktop-client/package.json
@@ -21,6 +21,7 @@
     "@swc/plugin-react-remove-properties": "^1.5.121",
     "@testing-library/react": "14.1.2",
     "@testing-library/user-event": "14.5.2",
+    "@types/lodash": "^4",
     "@types/promise-retry": "^1.1.6",
     "@types/react": "^18.2.0",
     "@types/react-dom": "^18.2.1",
@@ -41,6 +42,7 @@
     "inter-ui": "^3.19.3",
     "jest": "^27.5.1",
     "jest-watch-typeahead": "^2.2.2",
+    "lodash": "^4.17.21",
     "mdast-util-newline-to-break": "^2.0.0",
     "memoize-one": "^6.0.0",
     "pikaday": "1.8.2",
diff --git a/packages/desktop-client/src/components/reports/spreadsheets/net-worth-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/net-worth-spreadsheet.ts
index b5000d3e3..2fc4c1ae2 100644
--- a/packages/desktop-client/src/components/reports/spreadsheets/net-worth-spreadsheet.ts
+++ b/packages/desktop-client/src/components/reports/spreadsheets/net-worth-spreadsheet.ts
@@ -1,7 +1,8 @@
-// @ts-strict-ignore
 import * as d from 'date-fns';
+import keyBy from 'lodash/keyBy';
 
 import { runQuery } from 'loot-core/src/client/query-helpers';
+import { type useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
 import { send } from 'loot-core/src/platform/client/fetch';
 import * as monthUtils from 'loot-core/src/shared/months';
 import { q } from 'loot-core/src/shared/query';
@@ -10,21 +11,27 @@ import {
   integerToAmount,
   amountToInteger,
 } from 'loot-core/src/shared/util';
+import {
+  type AccountEntity,
+  type RuleConditionEntity,
+} from 'loot-core/types/models';
 
-import { index } from '../util';
+type Balance = {
+  date: string;
+  amount: number;
+};
 
 export function createSpreadsheet(
-  start,
-  end,
-  accounts,
-  conditions = [],
-  conditionsOp,
+  start: string,
+  end: string,
+  accounts: AccountEntity[],
+  conditions: RuleConditionEntity[] = [],
+  conditionsOp: 'and' | 'or',
 ) {
-  return async (spreadsheet, setData) => {
-    if (accounts.length === 0) {
-      return null;
-    }
-
+  return async (
+    spreadsheet: ReturnType<typeof useSpreadsheet>,
+    setData: (data: ReturnType<typeof recalculate>) => void,
+  ) => {
     const { filters } = await send('make-filters-from-conditions', {
       conditions: conditions.filter(cond => !cond.customName),
     });
@@ -32,7 +39,7 @@ export function createSpreadsheet(
 
     const data = await Promise.all(
       accounts.map(async acct => {
-        const [starting, balances] = await Promise.all([
+        const [starting, balances]: [number, Balance[]] = await Promise.all([
           runQuery(
             q('transactions')
               .filter({
@@ -65,7 +72,7 @@ export function createSpreadsheet(
 
         return {
           id: acct.id,
-          balances: index(balances, 'date'),
+          balances: keyBy(balances, 'date'),
           starting,
         };
       }),
@@ -75,7 +82,15 @@ export function createSpreadsheet(
   };
 }
 
-function recalculate(data, start, end) {
+function recalculate(
+  data: Array<{
+    id: string;
+    balances: Record<string, Balance>;
+    starting: number;
+  }>,
+  start: string,
+  end: string,
+) {
   const months = monthUtils.rangeInclusive(start, end);
 
   const accountBalances = data.map(account => {
@@ -92,10 +107,20 @@ function recalculate(data, start, end) {
   let hasNegative = false;
   let startNetWorth = 0;
   let endNetWorth = 0;
-  let lowestNetWorth = null;
-  let highestNetWorth = null;
-
-  const graphData = months.reduce((arr, month, idx) => {
+  let lowestNetWorth: number | null = null;
+  let highestNetWorth: number | null = null;
+
+  const graphData = months.reduce<
+    Array<{
+      x: string;
+      y: number;
+      assets: string;
+      debt: string;
+      change: string;
+      networth: string;
+      date: string;
+    }>
+  >((arr, month, idx) => {
     let debt = 0;
     let assets = 0;
     let total = 0;
@@ -134,10 +159,10 @@ function recalculate(data, start, end) {
     });
 
     arr.forEach(item => {
-      if (item.y < lowestNetWorth || lowestNetWorth === null) {
+      if (lowestNetWorth === null || item.y < lowestNetWorth) {
         lowestNetWorth = item.y;
       }
-      if (item.y > highestNetWorth || highestNetWorth === null) {
+      if (highestNetWorth === null || item.y > highestNetWorth) {
         highestNetWorth = item.y;
       }
     });
diff --git a/packages/desktop-client/src/components/reports/spreadsheets/spending-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/spending-spreadsheet.ts
index 4203f18cc..e844aa63c 100644
--- a/packages/desktop-client/src/components/reports/spreadsheets/spending-spreadsheet.ts
+++ b/packages/desktop-client/src/components/reports/spreadsheets/spending-spreadsheet.ts
@@ -1,5 +1,7 @@
 // @ts-strict-ignore
 
+import keyBy from 'lodash/keyBy';
+
 import { runQuery } from 'loot-core/src/client/query-helpers';
 import { type useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider';
 import { send } from 'loot-core/src/platform/client/fetch';
@@ -16,7 +18,6 @@ import {
 } from 'loot-core/src/types/models/reports';
 
 import { getSpecificRange } from '../reportRanges';
-import { index } from '../util';
 
 import { makeQuery } from './makeQuery';
 
@@ -157,7 +158,7 @@ export function createSpendingSpreadsheet({
           month: month.month,
         };
       });
-      const indexedData: SpendingMonthEntity = index(dayData, 'month');
+      const indexedData: SpendingMonthEntity = keyBy(dayData, 'month');
       return {
         months: indexedData,
         day,
diff --git a/packages/desktop-client/src/components/reports/util.ts b/packages/desktop-client/src/components/reports/util.ts
index d967a0bae..ab45b9bb5 100644
--- a/packages/desktop-client/src/components/reports/util.ts
+++ b/packages/desktop-client/src/components/reports/util.ts
@@ -18,18 +18,6 @@ export async function runAll(
   cb(data);
 }
 
-export function index<
-  T extends Record<string, string | number>,
-  K extends keyof T,
->(data: T[], field: K) {
-  const result: Record<string | number, T> = {};
-  data.forEach(item => {
-    const key = item[field];
-    result[key] = item;
-  });
-  return result;
-}
-
 export function indexCashFlow<
   T extends { date: string; isTransfer: boolean; amount: number },
 >(data: T[], date: string, isTransfer: string) {
diff --git a/upcoming-release-notes/2725.md b/upcoming-release-notes/2725.md
new file mode 100644
index 000000000..376fa4cbf
--- /dev/null
+++ b/upcoming-release-notes/2725.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [MatissJanis]
+---
+
+Do not show loading indicator in networth report if the budget file is empty
diff --git a/yarn.lock b/yarn.lock
index 2ac07467e..556d626fe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -73,6 +73,7 @@ __metadata:
     "@swc/plugin-react-remove-properties": "npm:^1.5.121"
     "@testing-library/react": "npm:14.1.2"
     "@testing-library/user-event": "npm:14.5.2"
+    "@types/lodash": "npm:^4"
     "@types/promise-retry": "npm:^1.1.6"
     "@types/react": "npm:^18.2.0"
     "@types/react-dom": "npm:^18.2.1"
@@ -93,6 +94,7 @@ __metadata:
     inter-ui: "npm:^3.19.3"
     jest: "npm:^27.5.1"
     jest-watch-typeahead: "npm:^2.2.2"
+    lodash: "npm:^4.17.21"
     mdast-util-newline-to-break: "npm:^2.0.0"
     memoize-one: "npm:^6.0.0"
     pikaday: "npm:1.8.2"
@@ -5493,6 +5495,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/lodash@npm:^4":
+  version: 4.17.1
+  resolution: "@types/lodash@npm:4.17.1"
+  checksum: 384bdd29348a000f8e815f94839a1a8c7f5a4ca856b016ade7f2abdc1df0b4e3e009c113b69db320a8fde51d1f38e60c19462b9bf3e82e0e2e32d3ac3e7ba2c4
+  languageName: node
+  linkType: hard
+
 "@types/mdast@npm:^3.0.0":
   version: 3.0.12
   resolution: "@types/mdast@npm:3.0.12"
-- 
GitLab