From 02ec279a64ee3e324d373e803d89c7bf5a2ebc85 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins <matiss@mja.lv> Date: Tue, 12 Mar 2024 22:04:25 +0000 Subject: [PATCH] :recycle: (typescript) make rollover budget components strict TS compliant (#2453) --- .../components/autocomplete/Autocomplete.tsx | 2 +- .../autocomplete/CategoryAutocomplete.tsx | 4 +- .../budget/rollover/BalanceTooltip.tsx | 15 ++++--- .../budget/rollover/CoverTooltip.tsx | 11 +++-- .../budget/rollover/HoldTooltip.tsx | 11 +++-- .../budget/rollover/RolloverComponents.tsx | 15 ++++--- .../budget/rollover/RolloverContext.tsx | 24 ++++++++--- .../budget/rollover/TransferTooltip.tsx | 21 +++++---- .../rollover/budgetsummary/BudgetSummary.tsx | 43 +++++++++++-------- .../rollover/budgetsummary/ToBudget.tsx | 11 +++-- .../src/components/budget/util.ts | 4 +- .../src/components/util/AmountInput.tsx | 4 +- packages/loot-core/src/shared/arithmetic.ts | 11 +++-- upcoming-release-notes/2453.md | 6 +++ 14 files changed, 107 insertions(+), 75 deletions(-) create mode 100644 upcoming-release-notes/2453.md diff --git a/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx b/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx index 0e923d8d4..ba3b746ff 100644 --- a/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx @@ -197,7 +197,7 @@ type SingleAutocompleteProps<T extends Item> = { onSelect: (id: T['id'], value: string) => void; tableBehavior?: boolean; closeOnBlur?: boolean; - value: T | T['id']; + value: null | T | T['id']; isMulti?: boolean; }; function SingleAutocomplete<T extends Item>({ diff --git a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx index de58e530e..d83f82de5 100644 --- a/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/CategoryAutocomplete.tsx @@ -115,7 +115,9 @@ function CategoryList({ ); } -type CategoryAutocompleteProps = ComponentProps<typeof Autocomplete> & { +type CategoryAutocompleteProps = ComponentProps< + typeof Autocomplete<CategoryGroupEntity> +> & { categoryGroups: Array<CategoryGroupEntity>; showSplitOption?: boolean; renderSplitTransactionButton?: ( diff --git a/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx index 6376207ca..5ad625120 100644 --- a/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BalanceTooltip.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { useState } from 'react'; import { rolloverBudget } from 'loot-core/src/client/queries'; @@ -67,11 +66,15 @@ export function BalanceTooltip({ ? 'Remove overspending rollover' : 'Rollover overspending', }, - balance < 0 && { - name: 'cover', - text: 'Cover overspending', - }, - ].filter(x => x)} + ...(balance < 0 + ? [ + { + name: 'cover', + text: 'Cover overspending', + }, + ] + : []), + ]} /> </Tooltip> )} diff --git a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx index 6ac8a4ab6..3ee4c086d 100644 --- a/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/CoverTooltip.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { type ComponentProps, useState } from 'react'; import { useCategories } from '../../../hooks/useCategories'; @@ -19,11 +18,11 @@ export function CoverTooltip({ onSubmit, onClose, }: CoverTooltipProps) { - let { grouped: categoryGroups } = useCategories(); - categoryGroups = addToBeBudgetedGroup( - categoryGroups.filter(g => !g.is_income), + const { grouped } = useCategories(); + const categoryGroups = addToBeBudgetedGroup( + grouped.filter(g => !g.is_income), ); - const [category, setCategory] = useState(null); + const [category, setCategory] = useState<string | null>(null); function submit() { if (category) { @@ -49,7 +48,7 @@ export function CoverTooltip({ value={null} openOnFocus={true} onUpdate={() => {}} - onSelect={id => setCategory(id)} + onSelect={(id: string | undefined) => setCategory(id || null)} inputProps={{ inputRef: node, onKeyDown: e => { diff --git a/packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx index 350c7a852..a2a4e6902 100644 --- a/packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/HoldTooltip.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { useState, useContext, @@ -30,7 +29,7 @@ export function HoldTooltip({ const spreadsheet = useSpreadsheet(); const sheetName = useContext(NamespaceContext); - const [amount, setAmount] = useState(null); + const [amount, setAmount] = useState<string | null>(null); useEffect(() => { (async () => { @@ -39,8 +38,8 @@ export function HoldTooltip({ })(); }, []); - function submit() { - const parsedAmount = evalArithmetic(amount, null); + function submit(newAmount: string) { + const parsedAmount = evalArithmetic(newAmount); if (parsedAmount) { onSubmit(amountToInteger(parsedAmount)); } @@ -68,7 +67,7 @@ export function HoldTooltip({ onChange={(e: ChangeEvent<HTMLInputElement>) => setAmount(e.target.value) } - onEnter={submit} + onEnter={() => submit(amount)} /> </InitialFocus> </View> @@ -85,7 +84,7 @@ export function HoldTooltip({ paddingTop: 3, paddingBottom: 3, }} - onClick={submit} + onClick={() => submit(amount)} > Hold </Button> diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx index 2fa62d106..a8e326f96 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { memo, useState } from 'react'; import { rolloverBudget } from 'loot-core/src/client/queries'; @@ -207,7 +206,7 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ width={14} height={14} className="hover-visible" - style={menuOpen && { opacity: 1 }} + style={menuOpen ? { opacity: 1 } : {}} /> </Button> {menuOpen && ( @@ -239,10 +238,14 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ name: 'set-single-12-avg', text: 'Set to yearly average', }, - isGoalTemplatesEnabled && { - name: 'apply-single-category-template', - text: 'Apply budget template', - }, + ...(isGoalTemplatesEnabled + ? [ + { + name: 'apply-single-category-template', + text: 'Apply budget template', + }, + ] + : []), ]} /> </Tooltip> diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx index fcbc714cd..583444a47 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverContext.tsx @@ -1,14 +1,28 @@ -// @ts-strict-ignore import React, { type ReactNode, createContext, useContext } from 'react'; import * as monthUtils from 'loot-core/src/shared/months'; -const Context = createContext(null); - -type RolloverContextProps = { +type RolloverContextDefinition = { summaryCollapsed: boolean; - onBudgetAction: (idx: number, action: string, arg?: unknown) => void; + onBudgetAction: (idx: string, action: string, arg?: unknown) => void; onToggleSummaryCollapse: () => void; + currentMonth: string; +}; + +const Context = createContext<RolloverContextDefinition>({ + summaryCollapsed: false, + onBudgetAction: () => { + throw new Error('Unitialised context method called: onBudgetAction'); + }, + onToggleSummaryCollapse: () => { + throw new Error( + 'Unitialised context method called: onToggleSummaryCollapse', + ); + }, + currentMonth: 'unknown', +}); + +type RolloverContextProps = Omit<RolloverContextDefinition, 'currentMonth'> & { children: ReactNode; }; export function RolloverContext({ diff --git a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx b/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx index 7806d7ce3..936ccfa3c 100644 --- a/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx +++ b/packages/desktop-client/src/components/budget/rollover/TransferTooltip.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { useState, useContext, @@ -24,10 +23,10 @@ type TransferTooltipProps = ComponentPropsWithoutRef<typeof Tooltip> & { initialAmount?: number; initialAmountName?: string; showToBeBudgeted?: boolean; - onSubmit: (amount: number, category: unknown) => void; + onSubmit: (amount: number, category: string) => void; }; export function TransferTooltip({ - initialAmount, + initialAmount = 0, initialAmountName, showToBeBudgeted, onSubmit, @@ -44,8 +43,8 @@ export function TransferTooltip({ categoryGroups = addToBeBudgetedGroup(categoryGroups); } - const [amount, setAmount] = useState(null); - const [category, setCategory] = useState(null); + const [amount, setAmount] = useState<string | null>(null); + const [category, setCategory] = useState<string | null>(null); useEffect(() => { (async () => { @@ -58,8 +57,8 @@ export function TransferTooltip({ })(); }, []); - function submit() { - const parsedAmount = evalArithmetic(amount, null); + function submit(newAmount: string) { + const parsedAmount = evalArithmetic(newAmount); if (parsedAmount && category) { onSubmit(amountToInteger(parsedAmount), category); onClose(); @@ -88,7 +87,7 @@ export function TransferTooltip({ <Input value={amount} onChange={e => setAmount(e.target['value'])} - onEnter={submit} + onEnter={() => submit(amount)} /> </InitialFocus> </View> @@ -99,8 +98,8 @@ export function TransferTooltip({ value={null} openOnFocus={true} onUpdate={() => {}} - onSelect={id => setCategory(id)} - inputProps={{ onEnter: submit, placeholder: '(none)' }} + onSelect={(id: string | undefined) => setCategory(id || null)} + inputProps={{ onEnter: () => submit(amount), placeholder: '(none)' }} showHiddenItems={true} /> @@ -117,7 +116,7 @@ export function TransferTooltip({ paddingTop: 3, paddingBottom: 3, }} - onClick={submit} + onClick={() => submit(amount)} > Transfer </Button> diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx index d612fe8ed..5a2a70c81 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { useState } from 'react'; import { css } from 'glamor'; @@ -161,22 +160,26 @@ export function BudgetSummary({ name: 'set-3-avg', text: 'Set budgets to 3 month average', }, - isGoalTemplatesEnabled && { - name: 'check-templates', - text: 'Check templates', - }, - isGoalTemplatesEnabled && { - name: 'apply-goal-template', - text: 'Apply budget template', - }, - isGoalTemplatesEnabled && { - name: 'overwrite-goal-template', - text: 'Overwrite with budget template', - }, - isGoalTemplatesEnabled && { - name: 'cleanup-goal-template', - text: 'End of month cleanup', - }, + ...(isGoalTemplatesEnabled + ? [ + { + name: 'check-templates', + text: 'Check templates', + }, + { + name: 'apply-goal-template', + text: 'Apply budget template', + }, + { + name: 'overwrite-goal-template', + text: 'Overwrite with budget template', + }, + { + name: 'cleanup-goal-template', + text: 'End of month cleanup', + }, + ] + : []), ]} /> </Tooltip> @@ -216,7 +219,11 @@ export function BudgetSummary({ }} /> <View style={{ margin: '23px 0' }}> - <ToBudget month={month} onBudgetAction={onBudgetAction} /> + <ToBudget + prevMonthName={prevMonthName} + month={month} + onBudgetAction={onBudgetAction} + /> </View> </> )} diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx index a07a2ee0f..02b1d1da6 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { useState, type ComponentPropsWithoutRef } from 'react'; import { css } from 'glamor'; @@ -21,9 +20,9 @@ import { TransferTooltip } from '../TransferTooltip'; import { TotalsList } from './TotalsList'; type ToBudgetProps = { - month: string | number; - onBudgetAction: (idx: string | number, action: string, arg?: unknown) => void; - prevMonthName?: string; + month: string; + onBudgetAction: (idx: string, action: string, arg?: unknown) => void; + prevMonthName: string; showTotalsTooltipOnHover?: boolean; style?: CSSProperties; amountStyle?: CSSProperties; @@ -44,7 +43,7 @@ export function ToBudget({ holdTooltipProps, transferTooltipProps, }: ToBudgetProps) { - const [menuOpen, setMenuOpen] = useState(null); + const [menuOpen, setMenuOpen] = useState<string | null>(null); const sheetName = useSheetName(rolloverBudget.toBudget); const sheetValue = useSheetValue({ name: rolloverBudget.toBudget, @@ -60,7 +59,7 @@ export function ToBudget({ <Block>{isNegative ? 'Overbudgeted:' : 'To Budget:'}</Block> <View> <HoverTarget - disabled={!showTotalsTooltipOnHover || menuOpen} + disabled={!showTotalsTooltipOnHover || !!menuOpen} renderContent={() => ( <Tooltip position="bottom-center" {...totalsTooltipProps}> <TotalsList diff --git a/packages/desktop-client/src/components/budget/util.ts b/packages/desktop-client/src/components/budget/util.ts index cb7b926b7..3c5bc8fe1 100644 --- a/packages/desktop-client/src/components/budget/util.ts +++ b/packages/desktop-client/src/components/budget/util.ts @@ -6,7 +6,7 @@ import { type Handlers } from 'loot-core/src/types/handlers'; import { type CategoryGroupEntity } from 'loot-core/src/types/models'; import { type LocalPrefs } from 'loot-core/src/types/prefs'; -import { styles, theme } from '../../style'; +import { type CSSProperties, styles, theme } from '../../style'; import { type DropPosition } from '../sort'; import { getValidMonthBounds } from './MonthsContext'; @@ -39,7 +39,7 @@ export function separateGroups(categoryGroups: CategoryGroupEntity[]) { ]; } -export function makeAmountGrey(value: number | string) { +export function makeAmountGrey(value: number | string): CSSProperties { return value === 0 || value === '0' || value === '' || value == null ? { color: theme.tableTextSubdued } : null; diff --git a/packages/desktop-client/src/components/util/AmountInput.tsx b/packages/desktop-client/src/components/util/AmountInput.tsx index 60269b440..e968423e3 100644 --- a/packages/desktop-client/src/components/util/AmountInput.tsx +++ b/packages/desktop-client/src/components/util/AmountInput.tsx @@ -69,9 +69,7 @@ export function AmountInput({ } function getAmount(negate) { - const valueOrInitial = Math.abs( - amountToInteger(evalArithmetic(value, initialValueAbsolute)), - ); + const valueOrInitial = Math.abs(amountToInteger(evalArithmetic(value))); return negate ? valueOrInitial * -1 : valueOrInitial; } diff --git a/packages/loot-core/src/shared/arithmetic.ts b/packages/loot-core/src/shared/arithmetic.ts index 935a0b229..ac8227864 100644 --- a/packages/loot-core/src/shared/arithmetic.ts +++ b/packages/loot-core/src/shared/arithmetic.ts @@ -96,12 +96,12 @@ function makeOperatorParser(...ops) { // These operators go from high to low order of precedence const parseOperator = makeOperatorParser('^', '/', '*', '-', '+'); -function parse(expression) { +function parse(expression: string) { const state = { str: expression.replace(/\s/g, ''), index: 0 }; return parseOperator(state); } -function evaluate(ast) { +function evaluate(ast): number { if (typeof ast === 'number') { return ast; } @@ -124,13 +124,16 @@ function evaluate(ast) { } } -export function evalArithmetic(expression, defaultValue = null) { +export function evalArithmetic( + expression: string, + defaultValue: number = null, +) { // An empty expression always evals to the default if (expression === '') { return defaultValue; } - let result; + let result: number; try { result = evaluate(parse(expression)); } catch (e) { diff --git a/upcoming-release-notes/2453.md b/upcoming-release-notes/2453.md new file mode 100644 index 000000000..2e5aff306 --- /dev/null +++ b/upcoming-release-notes/2453.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Enabled strict TypeScript in rollover budget components. -- GitLab