diff --git a/packages/desktop-client/src/components/forms.tsx b/packages/desktop-client/src/components/forms.tsx index a756aba2a91..96a11633b7a 100644 --- a/packages/desktop-client/src/components/forms.tsx +++ b/packages/desktop-client/src/components/forms.tsx @@ -64,7 +64,9 @@ export const FormField = ({ style, children }: FormFieldProps) => { // Custom inputs -type CheckboxProps = ComponentProps<'input'>; +type CheckboxProps = ComponentProps<'input'> & { + style?: CSSProperties; +}; export const Checkbox = (props: CheckboxProps) => { return ( diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.tsx similarity index 57% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.jsx rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.tsx index 76e1e7fb915..b7c74901a2f 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/CheckboxOption.tsx @@ -1,9 +1,18 @@ -import React from 'react'; +import React, { type ComponentProps, type ReactNode } from 'react'; -import { theme } from '../../../style'; +import { type CSSProperties, theme } from '../../../style'; import { View } from '../../common/View'; import { Checkbox } from '../../forms'; +type CheckboxOptionProps = { + id: string; + checked?: ComponentProps['checked']; + disabled?: ComponentProps['disabled']; + onChange?: ComponentProps['onChange']; + children: ReactNode; + style?: CSSProperties; +}; + export function CheckboxOption({ id, checked, @@ -11,7 +20,7 @@ export function CheckboxOption({ onChange, children, style, -}) { +}: CheckboxOptionProps) { return ( {children} diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.tsx similarity index 78% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.jsx rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.tsx index c911ea36c66..d743c71dedc 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/DateFormatSelect.tsx @@ -4,14 +4,25 @@ import { Select } from '../../common/Select'; import { View } from '../../common/View'; import { SectionLabel } from '../../forms'; -import { dateFormats } from './utils'; +import { + dateFormats, + type ImportTransaction, + type FieldMapping, +} from './utils'; + +type DateFormatSelectProps = { + transactions: ImportTransaction[]; + fieldMappings: FieldMapping; + parseDateFormat?: string; + onChange: (newValue: string) => void; +}; export function DateFormatSelect({ transactions, fieldMappings, parseDateFormat, onChange, -}) { +}: DateFormatSelectProps) { // We don't actually care about the delimiter, but we try to render // it based on the data we have so far. Look in a transaction and // try to figure out what delimiter the date is using, and default diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.tsx similarity index 87% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.jsx rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.tsx index b57e651dce7..e4a0ab4e80a 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/FieldMappings.tsx @@ -6,23 +6,44 @@ import { SectionLabel } from '../../forms'; import { SelectField } from './SelectField'; import { SubLabel } from './SubLabel'; -import { stripCsvImportTransaction } from './utils'; +import { + stripCsvImportTransaction, + type FieldMapping, + type ImportTransaction, +} from './utils'; + +type FieldMappingsProps = { + transactions: ImportTransaction[]; + mappings?: FieldMapping; + onChange: (field: keyof FieldMapping, newValue: string) => void; + splitMode: boolean; + inOutMode: boolean; + hasHeaderRow: boolean; +}; export function FieldMappings({ transactions, - mappings, + mappings = { + date: null, + amount: null, + payee: null, + notes: null, + inOut: null, + category: null, + outflow: null, + inflow: null, + }, onChange, splitMode, inOutMode, hasHeaderRow, -}) { +}: FieldMappingsProps) { if (transactions.length === 0) { return null; } const trans = stripCsvImportTransaction(transactions[0]); const options = Object.keys(trans); - mappings = mappings || {}; return ( diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.tsx similarity index 81% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.jsx rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.tsx index 5bd36a8f42e..80b565789ad 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/InOutOption.tsx @@ -5,13 +5,21 @@ import { View } from '../../common/View'; import { CheckboxOption } from './CheckboxOption'; +type InOutOptionProps = { + inOutMode: boolean; + outValue: string; + disabled: boolean; + onToggle: () => void; + onChangeText: (newValue: string) => void; +}; + export function InOutOption({ inOutMode, outValue, disabled, onToggle, onChangeText, -}) { +}: InOutOptionProps) { return ( ['onChange']; + onChangeAmount: (newValue: string) => void; +}; + export function MultiplierOption({ multiplierEnabled, multiplierAmount, onToggle, onChangeAmount, -}) { +}: MultiplierOptionProps) { return ( [1]; + dateFormat: Parameters[1]; + date?: string; +}; + +export function ParsedDate({ + parseDateFormat, + dateFormat, + date, +}: ParsedDateProps) { const parsed = date && formatDate( diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SelectField.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/SelectField.jsx deleted file mode 100644 index 0626b8e6393..00000000000 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SelectField.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -import { Select } from '../../common/Select'; - -export function SelectField({ - style, - options, - value, - onChange, - hasHeaderRow, - firstTransaction, -}) { - return ( - + [ + option, + hasHeaderRow + ? option + : `Column ${parseInt(option) + 1} (${firstTransaction[option]})`, + ] as const, + ), + ]} + value={value === null ? 'choose-field' : value} + onChange={onChange} + style={style} + /> + ); +} diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.tsx similarity index 70% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.jsx rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.tsx index 0d1d64de243..7994c94fcf7 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/SubLabel.tsx @@ -3,7 +3,11 @@ import React from 'react'; import { theme } from '../../../style'; import { Text } from '../../common/Text'; -export function SubLabel({ title }) { +type SubLabelProps = { + title: string; +}; + +export function SubLabel({ title }: SubLabelProps) { return ( {title} diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx similarity index 85% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.jsx rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx index 0a861eb02ad..14e456fc1a3 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/Transaction.tsx @@ -1,6 +1,7 @@ -import React, { useMemo } from 'react'; +import React, { type ComponentProps, useMemo } from 'react'; import { amountToCurrency } from 'loot-core/src/shared/util'; +import { type CategoryEntity } from 'loot-core/types/models'; import { SvgDownAndRightArrow } from '../../../icons/v2'; import { theme, styles } from '../../../style'; @@ -11,7 +12,29 @@ import { Checkbox } from '../../forms'; import { Row, Field } from '../../table'; import { ParsedDate } from './ParsedDate'; -import { applyFieldMappings, formatDate, parseAmountFields } from './utils'; +import { + applyFieldMappings, + type FieldMapping, + formatDate, + type ImportTransaction, + parseAmountFields, +} from './utils'; + +type TransactionProps = { + transaction: ImportTransaction; + fieldMappings: FieldMapping; + showParsed: boolean; + parseDateFormat: ComponentProps['parseDateFormat']; + dateFormat: ComponentProps['dateFormat']; + splitMode: boolean; + inOutMode: boolean; + outValue: number; + flipAmount: boolean; + multiplierAmount: string; + categories: CategoryEntity[]; + onCheckTransaction: (transactionId: string) => void; + reconcile: boolean; +}; export function Transaction({ transaction: rawTransaction, @@ -27,7 +50,7 @@ export function Transaction({ categories, onCheckTransaction, reconcile, -}) { +}: TransactionProps) { const categoryList = categories.map(category => category.name); const transaction = useMemo( () => @@ -37,23 +60,34 @@ export function Transaction({ [rawTransaction, fieldMappings], ); - let amount, outflow, inflow; - if (rawTransaction.isMatchedTransaction) { - amount = rawTransaction.amount; - if (splitMode) { - outflow = amount < 0 ? -amount : 0; - inflow = amount > 0 ? amount : 0; + const { amount, outflow, inflow } = useMemo(() => { + if (rawTransaction.isMatchedTransaction) { + const amount = rawTransaction.amount; + + return { + amount, + outflow: splitMode ? (amount < 0 ? -amount : 0) : null, + inflow: splitMode ? (amount > 0 ? amount : 0) : null, + }; } - } else { - ({ amount, outflow, inflow } = parseAmountFields( + + return parseAmountFields( transaction, splitMode, inOutMode, outValue, flipAmount, multiplierAmount, - )); - } + ); + }, [ + rawTransaction, + transaction, + splitMode, + inOutMode, + outValue, + flipAmount, + multiplierAmount, + ]); return ( {transaction.inOut} diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.test.js b/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.test.ts similarity index 93% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.test.js rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.test.ts index f4bfa6841cb..e6da6eba91e 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.test.js +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.test.ts @@ -1,8 +1,11 @@ import { parseDate } from './utils'; -describe('Import transactions', function () { - describe('date parsing', function () { - const invalidInputs = [ +describe('Import transactions', () => { + describe('date parsing', () => { + const invalidInputs: Array<{ + str: Parameters[0]; + order: Parameters[1]; + }> = [ { str: '', order: 'yyyy mm dd' }, { str: null, order: 'yyyy mm dd' }, { str: 42, order: 'yyyy mm dd' }, @@ -37,7 +40,10 @@ describe('Import transactions', function () { }, ); - const validInputs = [ + const validInputs: Array<{ + order: Parameters[1]; + cases: [Parameters[0], ReturnType][]; + }> = [ { order: 'yyyy mm dd', cases: [ diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.js b/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.ts similarity index 72% rename from packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.js rename to packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.ts index da881f10bba..7b057c7d824 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.js +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/utils.ts @@ -10,18 +10,27 @@ export const dateFormats = [ { format: 'mm dd yy', label: 'MM DD YY' }, { format: 'dd mm yyyy', label: 'DD MM YYYY' }, { format: 'dd mm yy', label: 'DD MM YY' }, -]; - -export function parseDate(str, order) { +] as const; + +export function parseDate( + str: string | number | null | Array | object, + order: + | 'yyyy mm dd' + | 'yy mm dd' + | 'mm dd yyyy' + | 'mm dd yy' + | 'dd mm yyyy' + | 'dd mm yy', +) { if (typeof str !== 'string') { return null; } - function pad(v) { + function pad(v: string) { return v && v.length === 1 ? '0' + v : v; } - const dateGroups = (a, b) => str => { + const dateGroups = (a: number, b: number) => (str: string) => { const parts = str .replace(/\bjan(\.|uary)?\b/i, '01') .replace(/\bfeb(\.|ruary)?\b/i, '02') @@ -48,7 +57,7 @@ export function parseDate(str, order) { const yearFirst = dateGroups(4, 2); const twoDig = dateGroups(2, 2); - let parts, year, month, day; + let parts: string[], year: string, month: string, day: string; switch (order) { case 'dd mm yyyy': parts = twoDig(str); @@ -95,7 +104,10 @@ export function parseDate(str, order) { return parsed; } -export function formatDate(date, format) { +export function formatDate( + date: Parameters[0] | null, + format: Parameters[1], +) { if (!date) { return null; } @@ -105,14 +117,36 @@ export function formatDate(date, format) { return null; } -export function applyFieldMappings(transaction, mappings) { - const result = {}; +export type ImportTransaction = { + trx_id: string; + existing: boolean; + ignored: boolean; + selected: boolean; + selected_merge: boolean; + amount: number; + inflow: number; + outflow: number; + inOut: number; +} & Record; + +export type FieldMapping = { + date: string | null; + amount: string | null; + payee: string | null; + notes: string | null; + inOut: string | null; + category: string | null; + outflow: string | null; + inflow: string | null; +}; + +export function applyFieldMappings( + transaction: ImportTransaction, + mappings: FieldMapping, +) { + const result: Partial = {}; for (const [originalField, target] of Object.entries(mappings)) { - let field = originalField; - if (field === 'payee') { - field = 'payee_name'; - } - + const field = originalField === 'payee' ? 'payee_name' : originalField; result[field] = transaction[target || field]; } // Keep preview fields on the mapped transactions @@ -121,10 +155,14 @@ export function applyFieldMappings(transaction, mappings) { result.ignored = transaction.ignored; result.selected = transaction.selected; result.selected_merge = transaction.selected_merge; - return result; + return result as ImportTransaction; } -function parseAmount(amount, mapper, multiplier) { +function parseAmount( + amount: number | string | undefined | null, + mapper: (parsed: number) => number, + multiplier: number, +) { if (amount == null) { return null; } @@ -140,12 +178,12 @@ function parseAmount(amount, mapper, multiplier) { } export function parseAmountFields( - trans, - splitMode, - inOutMode, - outValue, - flipAmount, - multiplierAmount, + trans: Partial, + splitMode: boolean, + inOutMode: boolean, + outValue: number, + flipAmount: boolean, + multiplierAmount: string, ) { const multiplier = parseFloat(multiplierAmount) || 1.0; @@ -186,7 +224,7 @@ export function parseAmountFields( }; } -export function stripCsvImportTransaction(transaction) { +export function stripCsvImportTransaction(transaction: ImportTransaction) { const { existing, ignored, selected, selected_merge, trx_id, ...trans } = transaction; diff --git a/upcoming-release-notes/3570.md b/upcoming-release-notes/3570.md new file mode 100644 index 00000000000..1d62319d47e --- /dev/null +++ b/upcoming-release-notes/3570.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +TypeScript: migrate smaller ImportTransactionsModal components to TS. diff --git a/upcoming-release-notes/3603.md b/upcoming-release-notes/3603.md index 4df7d555493..90920fca877 100644 --- a/upcoming-release-notes/3603.md +++ b/upcoming-release-notes/3603.md @@ -3,4 +3,4 @@ category: Bugfix authors: [wysinder] --- -Fixes inaccurate running balance when hiding reconciled transactions \ No newline at end of file +Fixes inaccurate running balance when hiding reconciled transactions