Skip to content

Commit

Permalink
Convert BudgetTable.jsx to tsx (#3899)
Browse files Browse the repository at this point in the history
* Convert BudgetTable to TypeScript

* Release notes
  • Loading branch information
joel-jeremy authored Dec 10, 2024
1 parent 6ea7732 commit 5104a1a
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,64 @@
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,
getScrollbarWidth,
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,
Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -77,35 +125,47 @@ 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;
}
}

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,
...findSortDown(expenseGroups, dropPos, targetId),
});
};

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);
Expand All @@ -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 {
Expand All @@ -127,7 +190,7 @@ export function BudgetTable(props) {
}
};

const onKeyDown = e => {
const onKeyDown = (e: KeyboardEvent) => {
if (!editing) {
return null;
}
Expand All @@ -138,7 +201,7 @@ export function BudgetTable(props) {
}
};

const onCollapse = collapsedIds => {
const onCollapse = (collapsedIds: string[]) => {
setCollapsedGroupIdsPref(collapsedIds);
};

Expand Down Expand Up @@ -223,6 +286,7 @@ export function BudgetTable(props) {
onKeyDown={onKeyDown}
>
<BudgetCategories
// @ts-expect-error Fix when migrating BudgetCategories to ts
categoryGroups={categoryGroups}
editingCell={editing}
dataComponents={dataComponents}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const DynamicBudgetTableInner = ({
onMonthSelect={_onMonthSelect}
/>
<BudgetTable
type={type}
prewarmStartMonth={prewarmStartMonth}
startMonth={startMonth}
numMonths={numMonths}
Expand All @@ -144,7 +145,13 @@ const DynamicBudgetTableInner = ({

DynamicBudgetTableInner.displayName = 'DynamicBudgetTableInner';

type DynamicBudgetTableProps = ComponentProps<typeof BudgetTable>;
type DynamicBudgetTableProps = Omit<
ComponentProps<typeof BudgetTable>,
'numMonths'
> & {
maxMonths: number;
onMonthSelect: (month: string, numMonths: number) => void;
};

export const DynamicBudgetTable = (props: DynamicBudgetTableProps) => {
return (
Expand Down
4 changes: 2 additions & 2 deletions packages/desktop-client/src/components/budget/MonthPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
Expand All @@ -29,7 +29,7 @@ export const MonthsContext = createContext<MonthsContextProps>(null);
type MonthsProviderProps = {
startMonth: string | undefined;
numMonths: number;
monthBounds: BoundsProps;
monthBounds: MonthBounds;
type: string;
children: ReactNode;
};
Expand Down
3 changes: 2 additions & 1 deletion packages/desktop-client/src/components/budget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ function BudgetInner(props: BudgetInnerProps) {
onShowActivity={onShowActivity}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
/>
</TrackingBudgetProvider>
);
Expand All @@ -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}
/>
</EnvelopeBudgetProvider>
);
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/3899.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [joel-jeremy]
---

Convert BudgetTable.jsx to TypeScript

0 comments on commit 5104a1a

Please sign in to comment.