From f1f1ba87e25e6475578b633fec4144b036b5a6fe Mon Sep 17 00:00:00 2001
From: Neil <55785687+carkom@users.noreply.github.com>
Date: Tue, 14 May 2024 18:14:35 +0100
Subject: [PATCH] Custom Reports: TS strict changes #2 (#2727)

* TS strict changes

* notes

* TS strict custom spreadsheets

* notes

* render row changes

* update report

* notes
---
 .../src/components/reports/ReportOptions.ts   |  38 +++--
 .../src/components/reports/SaveReport.tsx     |  18 +-
 .../spreadsheets/custom-spreadsheet.ts        | 160 +++++++++---------
 .../reports/spreadsheets/filterEmptyRows.ts   |  16 +-
 .../spreadsheets/grouped-spreadsheet.ts       |   6 +-
 upcoming-release-notes/2727.md                |   6 +
 6 files changed, 141 insertions(+), 103 deletions(-)
 create mode 100644 upcoming-release-notes/2727.md

diff --git a/packages/desktop-client/src/components/reports/ReportOptions.ts b/packages/desktop-client/src/components/reports/ReportOptions.ts
index e8c7bd8be..0ae9c6b0a 100644
--- a/packages/desktop-client/src/components/reports/ReportOptions.ts
+++ b/packages/desktop-client/src/components/reports/ReportOptions.ts
@@ -226,7 +226,10 @@ export type QueryDataEntity = {
   amount: number;
 };
 
-export type UncategorizedEntity = Pick<CategoryEntity, 'name' | 'hidden'> & {
+export type UncategorizedEntity = Pick<
+  CategoryEntity,
+  'id' | 'name' | 'hidden'
+> & {
   /*
     When looking at uncategorized and hidden transactions we
     need a way to group them. To do this we give them a unique
@@ -241,6 +244,7 @@ export type UncategorizedEntity = Pick<CategoryEntity, 'name' | 'hidden'> & {
 };
 
 const uncategorizedCategory: UncategorizedEntity = {
+  id: '',
   name: 'Uncategorized',
   uncategorized_id: '1',
   hidden: false,
@@ -249,6 +253,7 @@ const uncategorizedCategory: UncategorizedEntity = {
   has_category: false,
 };
 const transferCategory: UncategorizedEntity = {
+  id: '',
   name: 'Transfers',
   uncategorized_id: '2',
   hidden: false,
@@ -257,6 +262,7 @@ const transferCategory: UncategorizedEntity = {
   has_category: false,
 };
 const offBudgetCategory: UncategorizedEntity = {
+  id: '',
   name: 'Off Budget',
   uncategorized_id: '3',
   hidden: false,
@@ -283,7 +289,7 @@ export const categoryLists = (categories: {
   list: CategoryEntity[];
   grouped: CategoryGroupEntity[];
 }) => {
-  const categoryList = [
+  const categoryList: UncategorizedEntity[] = [
     ...categories.list.sort((a, b) => {
       //The point of this sorting is to make the graphs match the "budget" page
       const catGroupA = categories.grouped.find(f => f.id === a.cat_group);
@@ -304,34 +310,46 @@ export const categoryLists = (categories: {
     transferCategory,
   ];
 
-  const categoryGroup = [...categories.grouped, uncategorizedGroup];
+  const categoryGroup: UncategorizedGroupEntity[] = [
+    ...categories.grouped,
+    uncategorizedGroup,
+  ];
   return [categoryList, categoryGroup.filter(group => group !== null)] as const;
 };
 
 export const groupBySelections = (
   groupBy: string,
-  categoryList: CategoryEntity[],
+  categoryList: UncategorizedEntity[],
   categoryGroup: CategoryGroupEntity[],
   payees: PayeeEntity[],
   accounts: AccountEntity[],
-) => {
-  let groupByList;
-  let groupByLabel;
+): [
+  UncategorizedEntity[],
+  'category' | 'categoryGroup' | 'payee' | 'account',
+] => {
+  let groupByList: UncategorizedEntity[];
+  let groupByLabel: 'category' | 'categoryGroup' | 'payee' | 'account';
   switch (groupBy) {
     case 'Category':
       groupByList = categoryList;
       groupByLabel = 'category';
       break;
     case 'Group':
-      groupByList = categoryGroup;
+      groupByList = categoryGroup.map(group => {
+        return { id: group.id, name: group.name, hidden: group.hidden };
+      });
       groupByLabel = 'categoryGroup';
       break;
     case 'Payee':
-      groupByList = payees;
+      groupByList = payees.map(payee => {
+        return { id: payee.id, name: payee.name, hidden: false };
+      });
       groupByLabel = 'payee';
       break;
     case 'Account':
-      groupByList = accounts;
+      groupByList = accounts.map(account => {
+        return { id: account.id, name: account.name, hidden: false };
+      });
       groupByLabel = 'account';
       break;
     case 'Interval':
diff --git a/packages/desktop-client/src/components/reports/SaveReport.tsx b/packages/desktop-client/src/components/reports/SaveReport.tsx
index af27b31e1..d5edb18d1 100644
--- a/packages/desktop-client/src/components/reports/SaveReport.tsx
+++ b/packages/desktop-client/src/components/reports/SaveReport.tsx
@@ -40,14 +40,14 @@ export function SaveReport({
   const [chooseMenuOpen, setChooseMenuOpen] = useState(false);
   const [menuItem, setMenuItem] = useState('');
   const [err, setErr] = useState('');
-  const [name, setName] = useState(report.name ?? '');
+  const [newName, setNewName] = useState(report.name ?? '');
   const inputRef = createRef<HTMLInputElement>();
 
   async function onApply(cond: string) {
     const chooseSavedReport = listReports.find(r => cond === r.id);
     onReportChange({ savedReport: chooseSavedReport, type: 'choose' });
     setChooseMenuOpen(false);
-    setName(chooseSavedReport === undefined ? '' : chooseSavedReport.name);
+    setNewName(chooseSavedReport === undefined ? '' : chooseSavedReport.name);
   }
 
   const onAddUpdate = async ({ menuChoice }: { menuChoice?: string }) => {
@@ -58,7 +58,7 @@ export function SaveReport({
       const newSavedReport = {
         ...report,
         ...customReportItems,
-        name,
+        name: newName,
       };
 
       const response = await sendCatch('report/create', newSavedReport);
@@ -80,9 +80,11 @@ export function SaveReport({
       return;
     }
 
+    const { name, id, ...props } = customReportItems;
+
     const updatedReport = {
       ...report,
-      ...(menuChoice === 'rename-report' ? { name } : customReportItems),
+      ...(menuChoice === 'rename-report' ? { name: newName } : props),
     };
 
     const response = await sendCatch('report/update', updatedReport);
@@ -100,7 +102,7 @@ export function SaveReport({
   };
 
   const onDelete = async () => {
-    setName('');
+    setNewName('');
     await send('report/delete', report.id);
     onReportChange({ type: 'reset' });
     setDeleteMenuOpen(false);
@@ -134,7 +136,7 @@ export function SaveReport({
         break;
       case 'reset-report':
         setMenuOpen(false);
-        setName('');
+        setNewName('');
         onReportChange({ type: 'reset' });
         break;
       case 'choose-report':
@@ -185,8 +187,8 @@ export function SaveReport({
         <SaveReportName
           onClose={() => setNameMenuOpen(false)}
           menuItem={menuItem}
-          name={name}
-          setName={setName}
+          name={newName}
+          setName={setNewName}
           inputRef={inputRef}
           onAddUpdate={onAddUpdate}
           err={err}
diff --git a/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts
index 149c56785..ad734c270 100644
--- a/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts
+++ b/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts
@@ -1,4 +1,3 @@
-// @ts-strict-ignore
 import * as d from 'date-fns';
 
 import { runQuery } from 'loot-core/src/client/query-helpers';
@@ -13,13 +12,19 @@ import {
   type RuleConditionEntity,
   type CategoryGroupEntity,
 } from 'loot-core/src/types/models';
-import { type DataEntity } from 'loot-core/src/types/models/reports';
+import {
+  type DataEntity,
+  type GroupedEntity,
+  type IntervalEntity,
+} from 'loot-core/src/types/models/reports';
 import { type LocalPrefs } from 'loot-core/types/prefs';
 
 import {
   categoryLists,
   groupBySelections,
+  type QueryDataEntity,
   ReportOptions,
+  type UncategorizedEntity,
 } from '../ReportOptions';
 
 import { calculateLegend } from './calculateLegend';
@@ -61,10 +66,10 @@ export function createCustomSpreadsheet({
   showOffBudget,
   showHiddenCategories,
   showUncategorized,
-  groupBy,
+  groupBy = '',
   balanceTypeOp = 'totalDebts',
-  payees,
-  accounts,
+  payees = [],
+  accounts = [],
   graphType,
   firstDayOfWeekIdx,
   setDataCheck,
@@ -79,13 +84,10 @@ export function createCustomSpreadsheet({
       ),
   );
 
-  const [groupByList, groupByLabel] = groupBySelections(
-    groupBy,
-    categoryList,
-    categoryGroup,
-    payees,
-    accounts,
-  );
+  const [groupByList, groupByLabel]: [
+    groupByList: UncategorizedEntity[],
+    groupByLabel: 'category' | 'categoryGroup' | 'payee' | 'account',
+  ] = groupBySelections(groupBy, categoryList, categoryGroup, payees, accounts);
 
   return async (
     spreadsheet: ReturnType<typeof useSpreadsheet>,
@@ -100,7 +102,9 @@ export function createCustomSpreadsheet({
     });
     const conditionsOpKey = conditionsOp === 'or' ? '$or' : '$and';
 
-    let [assets, debts] = await Promise.all([
+    let assets: QueryDataEntity[];
+    let debts: QueryDataEntity[];
+    [assets, debts] = await Promise.all([
       runQuery(
         makeQuery(
           'assets',
@@ -143,83 +147,85 @@ export function createCustomSpreadsheet({
     const intervals =
       interval === 'Weekly'
         ? monthUtils.weekRangeInclusive(startDate, endDate, firstDayOfWeekIdx)
-        : monthUtils[ReportOptions.intervalRange.get(interval)](
-            startDate,
-            endDate,
-          );
+        : monthUtils[
+            ReportOptions.intervalRange.get(interval) || 'rangeInclusive'
+          ](startDate, endDate);
 
     let totalAssets = 0;
     let totalDebts = 0;
 
-    const intervalData = intervals.reduce((arr, intervalItem) => {
-      let perIntervalAssets = 0;
-      let perIntervalDebts = 0;
-      const stacked = {};
+    const intervalData = intervals.reduce(
+      (arr: IntervalEntity[], intervalItem) => {
+        let perIntervalAssets = 0;
+        let perIntervalDebts = 0;
+        const stacked: Record<string, number> = {};
 
-      groupByList.map(item => {
-        let stackAmounts = 0;
+        groupByList.map(item => {
+          let stackAmounts = 0;
 
-        const intervalAssets = filterHiddenItems(
-          item,
-          assets,
-          showOffBudget,
-          showHiddenCategories,
-          showUncategorized,
-        )
-          .filter(
-            asset =>
-              asset.date === intervalItem &&
-              asset[groupByLabel] === (item.id ?? null),
+          const intervalAssets = filterHiddenItems(
+            item,
+            assets,
+            showOffBudget,
+            showHiddenCategories,
+            showUncategorized,
           )
-          .reduce((a, v) => (a = a + v.amount), 0);
-        perIntervalAssets += intervalAssets;
+            .filter(
+              asset =>
+                asset.date === intervalItem &&
+                asset[groupByLabel] === (item.id ?? null),
+            )
+            .reduce((a, v) => (a = a + v.amount), 0);
+          perIntervalAssets += intervalAssets;
 
-        const intervalDebts = filterHiddenItems(
-          item,
-          debts,
-          showOffBudget,
-          showHiddenCategories,
-          showUncategorized,
-        )
-          .filter(
-            debt =>
-              debt.date === intervalItem &&
-              debt[groupByLabel] === (item.id ?? null),
+          const intervalDebts = filterHiddenItems(
+            item,
+            debts,
+            showOffBudget,
+            showHiddenCategories,
+            showUncategorized,
           )
-          .reduce((a, v) => (a = a + v.amount), 0);
-        perIntervalDebts += intervalDebts;
+            .filter(
+              debt =>
+                debt.date === intervalItem &&
+                debt[groupByLabel] === (item.id ?? null),
+            )
+            .reduce((a, v) => (a = a + v.amount), 0);
+          perIntervalDebts += intervalDebts;
 
-        if (balanceTypeOp === 'totalAssets') {
-          stackAmounts += intervalAssets;
-        }
-        if (balanceTypeOp === 'totalDebts') {
-          stackAmounts += intervalDebts;
-        }
-        if (stackAmounts !== 0) {
-          stacked[item.name] = integerToAmount(Math.abs(stackAmounts));
-        }
+          if (balanceTypeOp === 'totalAssets') {
+            stackAmounts += intervalAssets;
+          }
+          if (balanceTypeOp === 'totalDebts') {
+            stackAmounts += intervalDebts;
+          }
+          if (stackAmounts !== 0) {
+            stacked[item.name] = integerToAmount(Math.abs(stackAmounts));
+          }
 
-        return null;
-      });
-      totalAssets += perIntervalAssets;
-      totalDebts += perIntervalDebts;
+          return null;
+        });
+        totalAssets += perIntervalAssets;
+        totalDebts += perIntervalDebts;
 
-      arr.push({
-        date: d.format(
-          d.parseISO(intervalItem),
-          ReportOptions.intervalFormat.get(interval),
-        ),
-        ...stacked,
-        dateStart: intervalItem,
-        totalDebts: integerToAmount(perIntervalDebts),
-        totalAssets: integerToAmount(perIntervalAssets),
-        totalTotals: integerToAmount(perIntervalDebts + perIntervalAssets),
-      });
+        arr.push({
+          date: d.format(
+            d.parseISO(intervalItem),
+            ReportOptions.intervalFormat.get(interval) || '',
+          ),
+          ...stacked,
+          dateStart: intervalItem,
+          totalDebts: integerToAmount(perIntervalDebts),
+          totalAssets: integerToAmount(perIntervalAssets),
+          totalTotals: integerToAmount(perIntervalDebts + perIntervalAssets),
+        });
 
-      return arr;
-    }, []);
+        return arr;
+      },
+      [],
+    );
 
-    const calcData = groupByList.map(item => {
+    const calcData: GroupedEntity[] = groupByList.map(item => {
       const calc = recalculate({
         item,
         intervals,
@@ -233,7 +239,7 @@ export function createCustomSpreadsheet({
       return { ...calc };
     });
     const calcDataFiltered = calcData.filter(i =>
-      filterEmptyRows(showEmpty, i, balanceTypeOp),
+      filterEmptyRows({ showEmpty, data: i, balanceTypeOp }),
     );
 
     const legend = calculateLegend(
diff --git a/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts b/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts
index 2e1d9ca98..02a48e459 100644
--- a/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts
+++ b/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts
@@ -1,10 +1,14 @@
-import { type DataEntity } from 'loot-core/src/types/models/reports';
+import { type GroupedEntity } from 'loot-core/src/types/models/reports';
 
-export function filterEmptyRows(
-  showEmpty: boolean,
-  data: DataEntity,
-  balanceTypeOp: keyof DataEntity,
-): boolean {
+export function filterEmptyRows({
+  showEmpty,
+  data,
+  balanceTypeOp = 'totalDebts',
+}: {
+  showEmpty: boolean;
+  data: GroupedEntity;
+  balanceTypeOp?: keyof GroupedEntity;
+}): boolean {
   let showHide: boolean;
   if (balanceTypeOp === 'totalTotals') {
     showHide =
diff --git a/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts
index 9bd7b45d9..0995a14fc 100644
--- a/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts
+++ b/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts
@@ -176,14 +176,16 @@ export function createGroupedSpreadsheet({
           totalTotals: integerToAmount(totalAssets + totalDebts),
           intervalData,
           categories: stackedCategories.filter(i =>
-            filterEmptyRows(showEmpty, i, balanceTypeOp),
+            filterEmptyRows({ showEmpty, data: i, balanceTypeOp }),
           ),
         };
       },
       [startDate, endDate],
     );
     setData(
-      groupedData.filter(i => filterEmptyRows(showEmpty, i, balanceTypeOp)),
+      groupedData.filter(i =>
+        filterEmptyRows({ showEmpty, data: i, balanceTypeOp }),
+      ),
     );
   };
 }
diff --git a/upcoming-release-notes/2727.md b/upcoming-release-notes/2727.md
new file mode 100644
index 000000000..cc69a354f
--- /dev/null
+++ b/upcoming-release-notes/2727.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [carkom]
+---
+
+Making files in custom reports to comply with TS strict - stage #2
-- 
GitLab