diff --git a/packages/desktop-client/src/components/budget/BudgetTable.jsx b/packages/desktop-client/src/components/budget/BudgetTable.tsx
similarity index 65%
rename from packages/desktop-client/src/components/budget/BudgetTable.jsx
rename to packages/desktop-client/src/components/budget/BudgetTable.tsx
index 785457f2ab5..66481fd64af 100644
--- a/packages/desktop-client/src/components/budget/BudgetTable.jsx
+++ b/packages/desktop-client/src/components/budget/BudgetTable.tsx
@@ -1,14 +1,24 @@
-import React, { useState } from 'react';
+import React, {
+ type ComponentPropsWithoutRef,
+ type KeyboardEvent,
+ useState,
+} from 'react';
+
+import {
+ type CategoryEntity,
+ type CategoryGroupEntity,
+} from 'loot-core/types/models';
import { useCategories } from '../../hooks/useCategories';
import { useLocalPref } from '../../hooks/useLocalPref';
import { theme, styles } from '../../style';
import { View } from '../common/View';
+import { type DropPosition } from '../sort';
import { BudgetCategories } from './BudgetCategories';
import { BudgetSummaries } from './BudgetSummaries';
import { BudgetTotals } from './BudgetTotals';
-import { MonthsProvider } from './MonthsContext';
+import { type MonthBounds, MonthsProvider } from './MonthsContext';
import {
findSortDown,
findSortUp,
@@ -16,7 +26,39 @@ import {
separateGroups,
} from './util';
-export function BudgetTable(props) {
+type BudgetTableProps = {
+ type: string;
+ prewarmStartMonth: string;
+ startMonth: string;
+ numMonths: number;
+ monthBounds: MonthBounds;
+ dataComponents: {
+ SummaryComponent: ComponentPropsWithoutRef<
+ typeof BudgetSummaries
+ >['SummaryComponent'];
+ BudgetTotalsComponent: ComponentPropsWithoutRef<
+ typeof BudgetTotals
+ >['MonthComponent'];
+ };
+ onSaveCategory: (category: CategoryEntity) => void;
+ onDeleteCategory: (id: CategoryEntity['id']) => void;
+ onSaveGroup: (group: CategoryGroupEntity) => void;
+ onDeleteGroup: (id: CategoryGroupEntity['id']) => void;
+ onApplyBudgetTemplatesInGroup: (groupId: CategoryGroupEntity['id']) => void;
+ onReorderCategory: (params: {
+ id: CategoryEntity['id'];
+ groupId?: CategoryGroupEntity['id'];
+ targetId: CategoryEntity['id'] | null;
+ }) => void;
+ onReorderGroup: (params: {
+ id: CategoryGroupEntity['id'];
+ targetId: CategoryEntity['id'] | null;
+ }) => void;
+ onShowActivity: (id: CategoryEntity['id'], month?: string) => void;
+ onBudgetAction: (month: string, type: string, args: unknown) => void;
+};
+
+export function BudgetTable(props: BudgetTableProps) {
const {
type,
prewarmStartMonth,
@@ -35,23 +77,29 @@ export function BudgetTable(props) {
onBudgetAction,
} = props;
- const { grouped: categoryGroups } = useCategories();
+ const { grouped: categoryGroups = [] } = useCategories();
const [collapsedGroupIds = [], setCollapsedGroupIdsPref] =
useLocalPref('budget.collapsed');
const [showHiddenCategories, setShowHiddenCategoriesPef] = useLocalPref(
'budget.showHiddenCategories',
);
- const [editing, setEditing] = useState(null);
+ const [editing, setEditing] = useState<{ id: string; cell: string } | null>(
+ null,
+ );
- const onEditMonth = (id, month) => {
+ const onEditMonth = (id: string, month: string) => {
setEditing(id ? { id, cell: month } : null);
};
- const onEditName = id => {
+ const onEditName = (id: string) => {
setEditing(id ? { id, cell: 'name' } : null);
};
- const _onReorderCategory = (id, dropPos, targetId) => {
+ const _onReorderCategory = (
+ id: string,
+ dropPos: DropPosition,
+ targetId: string,
+ ) => {
const isGroup = !!categoryGroups.find(g => g.id === targetId);
if (isGroup) {
@@ -63,7 +111,7 @@ export function BudgetTable(props) {
const group = categoryGroups.find(g => g.id === groupId);
if (group) {
- const { categories } = group;
+ const { categories = [] } = group;
onReorderCategory({
id,
groupId: group.id,
@@ -77,7 +125,7 @@ export function BudgetTable(props) {
let targetGroup;
for (const group of categoryGroups) {
- if (group.categories.find(cat => cat.id === targetId)) {
+ if (group.categories?.find(cat => cat.id === targetId)) {
targetGroup = group;
break;
}
@@ -85,13 +133,17 @@ export function BudgetTable(props) {
onReorderCategory({
id,
- groupId: targetGroup.id,
- ...findSortDown(targetGroup.categories, dropPos, targetId),
+ groupId: targetGroup?.id,
+ ...findSortDown(targetGroup?.categories || [], dropPos, targetId),
});
}
};
- const _onReorderGroup = (id, dropPos, targetId) => {
+ const _onReorderGroup = (
+ id: string,
+ dropPos: DropPosition,
+ targetId: string,
+ ) => {
const [expenseGroups] = separateGroups(categoryGroups); // exclude Income group from sortable groups to fix off-by-one error
onReorderGroup({
id,
@@ -99,13 +151,21 @@ export function BudgetTable(props) {
});
};
- const moveVertically = dir => {
- const flattened = categoryGroups.reduce((all, group) => {
- if (collapsedGroupIds.includes(group.id)) {
- return all.concat({ id: group.id, isGroup: true });
- }
- return all.concat([{ id: group.id, isGroup: true }, ...group.categories]);
- }, []);
+ const moveVertically = (dir: 1 | -1) => {
+ const flattened = categoryGroups.reduce(
+ (all, group) => {
+ if (collapsedGroupIds.includes(group.id)) {
+ return all.concat({ id: group.id, isGroup: true });
+ }
+ return all.concat([
+ { id: group.id, isGroup: true },
+ ...(group?.categories || []),
+ ]);
+ },
+ [] as Array<
+ { id: CategoryGroupEntity['id']; isGroup: boolean } | CategoryEntity
+ >,
+ );
if (editing) {
const idx = flattened.findIndex(item => item.id === editing.id);
@@ -114,10 +174,13 @@ export function BudgetTable(props) {
while (nextIdx >= 0 && nextIdx < flattened.length) {
const next = flattened[nextIdx];
- if (next.isGroup) {
+ if ('isGroup' in next && next.isGroup) {
nextIdx += dir;
continue;
- } else if (type === 'report' || !next.is_income) {
+ } else if (
+ type === 'report' ||
+ ('is_income' in next && !next.is_income)
+ ) {
onEditMonth(next.id, editing.cell);
return;
} else {
@@ -127,7 +190,7 @@ export function BudgetTable(props) {
}
};
- const onKeyDown = e => {
+ const onKeyDown = (e: KeyboardEvent) => {
if (!editing) {
return null;
}
@@ -138,7 +201,7 @@ export function BudgetTable(props) {
}
};
- const onCollapse = collapsedIds => {
+ const onCollapse = (collapsedIds: string[]) => {
setCollapsedGroupIdsPref(collapsedIds);
};
@@ -223,6 +286,7 @@ export function BudgetTable(props) {
onKeyDown={onKeyDown}
>
;
+type DynamicBudgetTableProps = Omit<
+ ComponentProps,
+ 'numMonths'
+> & {
+ maxMonths: number;
+ onMonthSelect: (month: string, numMonths: number) => void;
+};
export const DynamicBudgetTable = (props: DynamicBudgetTableProps) => {
return (
diff --git a/packages/desktop-client/src/components/budget/MonthPicker.tsx b/packages/desktop-client/src/components/budget/MonthPicker.tsx
index 501bafd79ff..66f44423871 100644
--- a/packages/desktop-client/src/components/budget/MonthPicker.tsx
+++ b/packages/desktop-client/src/components/budget/MonthPicker.tsx
@@ -7,12 +7,12 @@ import { useResizeObserver } from '../../hooks/useResizeObserver';
import { styles, theme } from '../../style';
import { View } from '../common/View';
-import { type BoundsProps } from './MonthsContext';
+import { type MonthBounds } from './MonthsContext';
type MonthPickerProps = {
startMonth: string;
numDisplayed: number;
- monthBounds: BoundsProps;
+ monthBounds: MonthBounds;
style: CSSProperties;
onSelect: (month: string) => void;
};
diff --git a/packages/desktop-client/src/components/budget/MonthsContext.tsx b/packages/desktop-client/src/components/budget/MonthsContext.tsx
index 2d5e377402a..dad9b264028 100644
--- a/packages/desktop-client/src/components/budget/MonthsContext.tsx
+++ b/packages/desktop-client/src/components/budget/MonthsContext.tsx
@@ -3,13 +3,13 @@ import React, { createContext, type ReactNode } from 'react';
import * as monthUtils from 'loot-core/src/shared/months';
-export type BoundsProps = {
+export type MonthBounds = {
start: string;
end: string;
};
export function getValidMonthBounds(
- bounds: BoundsProps,
+ bounds: MonthBounds,
startMonth: undefined | string,
endMonth: string,
) {
@@ -29,7 +29,7 @@ export const MonthsContext = createContext(null);
type MonthsProviderProps = {
startMonth: string | undefined;
numMonths: number;
- monthBounds: BoundsProps;
+ monthBounds: MonthBounds;
type: string;
children: ReactNode;
};
diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx
index 8abaf8de55f..0012f46f0a7 100644
--- a/packages/desktop-client/src/components/budget/index.tsx
+++ b/packages/desktop-client/src/components/budget/index.tsx
@@ -355,6 +355,7 @@ function BudgetInner(props: BudgetInnerProps) {
onShowActivity={onShowActivity}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
+ onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
/>
);
@@ -375,13 +376,13 @@ function BudgetInner(props: BudgetInnerProps) {
onMonthSelect={onMonthSelect}
onDeleteCategory={onDeleteCategory}
onDeleteGroup={onDeleteGroup}
- onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
onSaveCategory={onSaveCategory}
onSaveGroup={onSaveGroup}
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivity}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
+ onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
/>
);
diff --git a/upcoming-release-notes/3899.md b/upcoming-release-notes/3899.md
new file mode 100644
index 00000000000..f330782869e
--- /dev/null
+++ b/upcoming-release-notes/3899.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [joel-jeremy]
+---
+
+Convert BudgetTable.jsx to TypeScript