diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index 7245a399d33..6e44db1be50 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -26,6 +26,15 @@ import { Text } from '../common/Text'; import { View } from '../common/View'; import { useMultiuserEnabled } from '../ServerContext'; +export type NormalizedAccount = { + id: string; + name: string; + institution: string; + orgDomain: string; + orgId: string; + balance: number; +}; + type CreateAccountProps = { upgradingAccountId?: string; }; @@ -77,20 +86,11 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) { throw new Error(results.reason); } - const newAccounts = []; - - type NormalizedAccount = { - account_id: string; - name: string; - institution: string; - orgDomain: string; - orgId: string; - balance: number; - }; + const newAccounts: NormalizedAccount[] = []; for (const oldAccount of results.accounts) { const newAccount: NormalizedAccount = { - account_id: oldAccount.id, + id: oldAccount.id, name: oldAccount.name, institution: oldAccount.org.name, orgDomain: oldAccount.org.domain, diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx similarity index 66% rename from packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx rename to packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx index 61a1aabd28a..e273ae60d7e 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { @@ -8,6 +8,10 @@ import { linkAccountSimpleFin, unlinkAccount, } from 'loot-core/client/actions'; +import { + type AccountEntity, + type AccountSyncSource, +} from 'loot-core/types/models/account'; import { useAccounts } from '../../hooks/useAccounts'; import { theme } from '../../style'; @@ -17,24 +21,51 @@ import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { PrivacyFilter } from '../PrivacyFilter'; -import { TableHeader, Table, Row, Field } from '../table'; +import { Field, Row, Table, TableHeader } from '../table'; + +import { type NormalizedAccount } from './CreateAccountModal'; + +type LinkAccountOption = { + id: string; + name: string; +}; -const addOnBudgetAccountOption = { id: 'new-on', name: 'Create new account' }; -const addOffBudgetAccountOption = { +const addOnBudgetAccountOption: LinkAccountOption = { + id: 'new-on', + name: 'Create new account', +}; + +const addOffBudgetAccountOption: LinkAccountOption = { id: 'new-off', name: 'Create new account (off budget)', }; +type SelectLinkedAccountsModalProps = { + requisitionId: string; + externalAccounts: NormalizedAccount[]; + syncSource: AccountSyncSource; +}; + +type LinkedAccountIds = { [key: string]: string }; +type LinkedAccountIdsSetter = ( + fn: (value: LinkedAccountIds) => LinkedAccountIds, +) => void; + export function SelectLinkedAccountsModal({ requisitionId, externalAccounts, syncSource, -}) { +}: SelectLinkedAccountsModalProps) { externalAccounts.sort((a, b) => a.name.localeCompare(b.name)); const { t } = useTranslation(); const dispatch = useDispatch(); - const localAccounts = useAccounts().filter(a => a.closed === 0); - const [chosenAccounts, setChosenAccounts] = useState(() => { + const localAccounts: AccountEntity[] = useAccounts().filter( + a => a.closed === 0, + ); + const [chosenAccounts, setChosenAccounts]: [ + LinkedAccountIds, + LinkedAccountIdsSetter, + ] = useState(() => { return Object.fromEntries( localAccounts .filter(acc => acc.account_id) @@ -43,7 +74,7 @@ export function SelectLinkedAccountsModal({ }); async function onNext() { - const chosenLocalAccountIds = Object.values(chosenAccounts); + const chosenLocalAccountIds: string[] = Object.values(chosenAccounts); // Unlink accounts that were previously linked, but the user // chose to remove the bank-sync @@ -56,7 +87,7 @@ export function SelectLinkedAccountsModal({ Object.entries(chosenAccounts).forEach( ([chosenExternalAccountId, chosenLocalAccountId]) => { const externalAccount = externalAccounts.find( - account => account.account_id === chosenExternalAccountId, + account => account.id === chosenExternalAccountId, ); const offBudget = chosenLocalAccountId === addOffBudgetAccountOption.id; @@ -101,14 +132,17 @@ export function SelectLinkedAccountsModal({ account => !Object.values(chosenAccounts).includes(account.id), ); - function onSetLinkedAccount(externalAccount, localAccountId) { - setChosenAccounts(accounts => { - const updatedAccounts = { ...accounts }; + function onSetLinkedAccount( + externalAccount: NormalizedAccount, + localAccountId: string | undefined, + ) { + setChosenAccounts((accounts: LinkedAccountIds): LinkedAccountIds => { + const updatedAccounts: LinkedAccountIds = { ...accounts }; - if (localAccountId) { - updatedAccounts[externalAccount.account_id] = localAccountId; + if (localAccountId !== undefined) { + updatedAccounts[externalAccount.id] = localAccountId; } else { - delete updatedAccounts[externalAccount.account_id]; + delete updatedAccounts[externalAccount.id]; } return updatedAccounts; @@ -138,32 +172,36 @@ export function SelectLinkedAccountsModal({ border: '1px solid ' + theme.tableBorder, }} > - - + + + Bank Account To Sync + + + Balance + + + Account in Actual + + + Actions + + index} - renderItem={({ key, item }) => ( - + getItemKey={index => externalAccounts[index].id} + renderItem={({ item }) => ( + chosenAccounts[item.account_id] === acc.id, + acc => chosenAccounts[item.id] === acc.id, ) } unlinkedAccounts={unlinkedAccounts} @@ -195,22 +233,39 @@ export function SelectLinkedAccountsModal({ ); } +type TableRowProps = { + externalAccount: NormalizedAccount; + chosenAccount: LinkAccountOption | undefined; + unlinkedAccounts: LinkAccountOption[]; + onSetLinkedAccount: ( + account: NormalizedAccount, + localAccountId: string | undefined, + ) => void; +}; + function TableRow({ externalAccount, chosenAccount, unlinkedAccounts, onSetLinkedAccount, -}) { +}: TableRowProps) { const { t } = useTranslation(); - const [focusedField, setFocusedField] = useState(null); + const [focusedField, setFocusedField] = useState(null); - const availableAccountOptions = [ + const availableAccountOptions: LinkAccountOption[] = [ ...unlinkedAccounts, - chosenAccount?.id !== addOnBudgetAccountOption.id && chosenAccount, addOnBudgetAccountOption, addOffBudgetAccountOption, ].filter(Boolean); + if ( + chosenAccount && + chosenAccount.id !== addOnBudgetAccountOption.id && + chosenAccount.id !== addOffBudgetAccountOption.id + ) { + availableAccountOptions.push(chosenAccount); + } + return ( {externalAccount.name} @@ -228,7 +283,7 @@ function TableRow({ strict highlightFirst suggestions={availableAccountOptions} - onSelect={value => { + onSelect={(value: string | undefined) => { onSetLinkedAccount(externalAccount, value); }} inputProps={{ @@ -244,7 +299,7 @@ function TableRow({ {chosenAccount ? (