diff --git a/packages/desktop-client/src/components/budget/BudgetTable.jsx b/packages/desktop-client/src/components/budget/BudgetTable.jsx index db7826460c0..cc61c3d8b5e 100644 --- a/packages/desktop-client/src/components/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/budget/BudgetTable.jsx @@ -1,9 +1,9 @@ -import React, { createRef, Component } from 'react'; -import { connect } from 'react-redux'; +import React, { useRef, useState } from 'react'; -import { savePrefs } from 'loot-core/src/client/actions'; import * as monthUtils from 'loot-core/src/shared/months'; +import { useCategories } from '../../hooks/useCategories'; +import { useLocalPref } from '../../hooks/useLocalPref'; import { theme, styles } from '../../style'; import { View } from '../common/View'; import { IntersectionBoundary } from '../tooltips'; @@ -14,28 +14,41 @@ import { BudgetTotals } from './BudgetTotals'; import { MonthsProvider } from './MonthsContext'; import { findSortDown, findSortUp, getScrollbarWidth } from './util'; -class BudgetTableInner extends Component { - constructor(props) { - super(props); - this.budgetCategoriesRef = createRef(); - - this.state = { - editing: null, - draggingState: null, - }; - } - - onEditMonth = (id, monthIndex) => { - this.setState({ editing: id ? { id, cell: monthIndex } : null }); +export function BudgetTable(props) { + const { + type, + prewarmStartMonth, + startMonth, + numMonths, + monthBounds, + dataComponents, + onSaveCategory, + onDeleteCategory, + onSaveGroup, + onDeleteGroup, + onReorderCategory, + onReorderGroup, + onShowActivity, + onBudgetAction, + } = props; + + const budgetCategoriesRef = useRef(); + const { grouped: categoryGroups } = useCategories(); + const [collapsed = [], setCollapsedPref] = useLocalPref('budget.collapsed'); + const [showHiddenCategories, setShowHiddenCategoriesPef] = useLocalPref( + 'budget.showHiddenCategories', + ); + const [editing, setEditing] = useState(null); + + const onEditMonth = (id, monthIndex) => { + setEditing(id ? { id, cell: monthIndex } : null); }; - onEditName = id => { - this.setState({ editing: id ? { id, cell: 'name' } : null }); + const onEditName = id => { + setEditing(id ? { id, cell: 'name' } : null); }; - onReorderCategory = (id, dropPos, targetId) => { - const { categoryGroups } = this.props; - + const _onReorderCategory = (id, dropPos, targetId) => { const isGroup = !!categoryGroups.find(g => g.id === targetId); if (isGroup) { @@ -48,7 +61,7 @@ class BudgetTableInner extends Component { if (group) { const { categories } = group; - this.props.onReorderCategory({ + onReorderCategory({ id, groupId: group.id, targetId: @@ -67,7 +80,7 @@ class BudgetTableInner extends Component { } } - this.props.onReorderCategory({ + onReorderCategory({ id, groupId: targetGroup.id, ...findSortDown(targetGroup.categories, dropPos, targetId), @@ -75,19 +88,14 @@ class BudgetTableInner extends Component { } }; - onReorderGroup = (id, dropPos, targetId) => { - const { categoryGroups } = this.props; - - this.props.onReorderGroup({ + const _onReorderGroup = (id, dropPos, targetId) => { + onReorderGroup({ id, ...findSortDown(categoryGroups, dropPos, targetId), }); }; - moveVertically = dir => { - const { editing } = this.state; - const { type, categoryGroups, collapsed } = this.props; - + const moveVertically = dir => { const flattened = categoryGroups.reduce((all, group) => { if (collapsed.includes(group.id)) { return all.concat({ id: group.id, isGroup: true }); @@ -106,7 +114,7 @@ class BudgetTableInner extends Component { nextIdx += dir; continue; } else if (type === 'report' || !next.is_income) { - this.onEditMonth(next.id, editing.cell); + onEditMonth(next.id, editing.cell); return; } else { break; @@ -115,187 +123,136 @@ class BudgetTableInner extends Component { } }; - onKeyDown = e => { - if (!this.state.editing) { + const onKeyDown = e => { + if (!editing) { return null; } if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); - this.moveVertically(e.shiftKey ? -1 : 1); + moveVertically(e.shiftKey ? -1 : 1); } }; - onShowActivity = (catId, monthIndex) => { - this.props.onShowActivity(catId, this.resolveMonth(monthIndex)); + const resolveMonth = monthIndex => { + return monthUtils.addMonths(startMonth, monthIndex); }; - onBudgetAction = (monthIndex, type, args) => { - this.props.onBudgetAction(this.resolveMonth(monthIndex), type, args); + const _onShowActivity = (catId, monthIndex) => { + onShowActivity(catId, resolveMonth(monthIndex)); }; - resolveMonth = monthIndex => { - return monthUtils.addMonths(this.props.startMonth, monthIndex); + const _onBudgetAction = (monthIndex, type, args) => { + onBudgetAction(resolveMonth(monthIndex), type, args); }; - // This is called via ref. - clearEditing() { - this.setState({ editing: null }); - } + const onCollapse = collapsedIds => { + setCollapsedPref(collapsedIds); + }; - toggleHiddenCategories = () => { - this.props.onToggleHiddenCategories(); + const onToggleHiddenCategories = () => { + setShowHiddenCategoriesPef(!showHiddenCategories); }; - expandAllCategories = () => { - this.props.onCollapse([]); + const toggleHiddenCategories = () => { + onToggleHiddenCategories(); }; - collapseAllCategories = () => { - const { onCollapse, categoryGroups } = this.props; - onCollapse(categoryGroups.map(g => g.id)); + const expandAllCategories = () => { + onCollapse([]); }; - render() { - const { - type, - categoryGroups, - prewarmStartMonth, - startMonth, - numMonths, - monthBounds, - dataComponents, - onSaveCategory, - onSaveGroup, - onDeleteCategory, - onDeleteGroup, - } = this.props; - const { editing, draggingState } = this.state; + const collapseAllCategories = () => { + onCollapse(categoryGroups.map(g => g.id)); + }; - return ( + return ( + - - - - - - - + - - + + + + + + + + - (this.budgetDataNode = el)} - > - - + - - - - ); - } + + + + + ); } -const mapStateToProps = state => { - const { grouped: categoryGroups } = state.queries.categories; - const collapsed = state.prefs.local?.['budget.collapsed'] || []; - return { - categoryGroups, - collapsed, - }; -}; - -const mapDispatchToProps = dispatch => { - const onCollapse = collapsedIds => { - dispatch(savePrefs({ 'budget.collapsed': collapsedIds })); - }; - - const onToggleHiddenCategories = () => - dispatch((innerDispatch, getState) => { - const { prefs } = getState(); - const showHiddenCategories = prefs.local['budget.showHiddenCategories']; - innerDispatch( - savePrefs({ - 'budget.showHiddenCategories': !showHiddenCategories, - }), - ); - }); - return { - onCollapse, - onToggleHiddenCategories, - }; -}; - -export const BudgetTable = connect(mapStateToProps, mapDispatchToProps, null, { - forwardRef: true, -})(BudgetTableInner); +BudgetTable.displayName = 'BudgetTable'; diff --git a/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx b/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx index 1422a021c97..60e97052b8e 100644 --- a/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx +++ b/packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx @@ -1,8 +1,7 @@ // @ts-strict-ignore -import React, { forwardRef, useEffect, type ComponentProps } from 'react'; +import React, { useEffect, type ComponentProps } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { useActions } from '../../hooks/useActions'; import { View } from '../common/View'; import { useBudgetMonthCount } from './BudgetMonthCountContext'; @@ -30,90 +29,72 @@ function getNumPossibleMonths(width: number) { type DynamicBudgetTableInnerProps = { width: number; height: number; -} & ComponentProps; +} & DynamicBudgetTableProps; -const DynamicBudgetTableInner = forwardRef< - typeof BudgetTable, - DynamicBudgetTableInnerProps ->( - ( - { - width, - height, - prewarmStartMonth, - startMonth, - maxMonths = 3, - monthBounds, - onMonthSelect: onMonthSelect_, - onPreload, - ...props - }, - ref, - ) => { - const { setDisplayMax } = useBudgetMonthCount(); - const actions = useActions(); +const DynamicBudgetTableInner = ({ + width, + height, + prewarmStartMonth, + startMonth, + maxMonths = 3, + monthBounds, + onMonthSelect, + ...props +}: DynamicBudgetTableInnerProps) => { + const { setDisplayMax } = useBudgetMonthCount(); - const numPossible = getNumPossibleMonths(width); - const numMonths = Math.min(numPossible, maxMonths); - const maxWidth = 200 + 500 * numMonths; + const numPossible = getNumPossibleMonths(width); + const numMonths = Math.min(numPossible, maxMonths); + const maxWidth = 200 + 500 * numMonths; - useEffect(() => { - setDisplayMax(numPossible); - }, [numPossible]); + useEffect(() => { + setDisplayMax(numPossible); + }, [numPossible]); - function onMonthSelect(month) { - onMonthSelect_(month, numMonths); - } + function _onMonthSelect(month) { + onMonthSelect(month, numMonths); + } - return ( - - - - - + return ( + + + + - ); - }, -); + + ); +}; DynamicBudgetTableInner.displayName = 'DynamicBudgetTableInner'; -export const DynamicBudgetTable = forwardRef< - typeof BudgetTable, - DynamicBudgetTableInnerProps ->((props, ref) => { +type DynamicBudgetTableProps = ComponentProps; + +export const DynamicBudgetTable = (props: DynamicBudgetTableProps) => { return ( {({ width, height }) => ( - + )} ); -}); +}; DynamicBudgetTable.displayName = 'DynamicBudgetTable'; diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx index ed36d976b25..2d339b755ad 100644 --- a/packages/desktop-client/src/components/budget/index.tsx +++ b/packages/desktop-client/src/components/budget/index.tsx @@ -1,12 +1,5 @@ // @ts-strict-ignore -import React, { - memo, - useContext, - useMemo, - useState, - useEffect, - useRef, -} from 'react'; +import React, { memo, useContext, useMemo, useState, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { @@ -69,16 +62,15 @@ type RolloverComponents = { IncomeHeaderComponent: typeof rollover.IncomeHeaderMonth; }; -type BudgetProps = { +type BudgetInnerProps = { accountId?: string; reportComponents: ReportComponents; rolloverComponents: RolloverComponents; titlebar: TitlebarContextValue; }; -function BudgetInner(props: BudgetProps) { +function BudgetInner(props: BudgetInnerProps) { const currentMonth = monthUtils.currentMonth(); - const tableRef = useRef(null); const spreadsheet = useSpreadsheet(); const dispatch = useDispatch(); const navigate = useNavigate(); @@ -138,17 +130,6 @@ function BudgetInner(props: BudgetProps) { }), listen('undo-event', ({ tables }) => { - if (tableRef.current) { - // g dammit - // We need to clear the editing cell, otherwise when - // the user navigates away from the page they will - // accidentally clear the undo stack if they have pressed - // undo, since the cell will save itself on blur (worst case: - // undo takes you to another screen and then you can't redo - // any of the budget changes) - tableRef.current.clearEditing(); - } - if (tables.includes('categories')) { loadCategories(); } @@ -377,7 +358,6 @@ function BudgetInner(props: BudgetProps) { onToggleSummaryCollapse={onToggleCollapse} >