From 4feb09259b4501462807da3bb197aa705e3c1d4f Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 23 Jan 2024 13:44:07 +0000 Subject: [PATCH 1/9] [TS Migration] Migrate WorkspaceCard to Typescript --- .../workspace/card/WorkspaceCardNoVBAView.js | 49 ------------------- .../workspace/card/WorkspaceCardNoVBAView.tsx | 40 +++++++++++++++ src/pages/workspace/card/WorkspaceCardPage.js | 47 ------------------ .../workspace/card/WorkspaceCardPage.tsx | 39 +++++++++++++++ ...iew.js => WorkspaceCardVBANoECardView.tsx} | 48 +++++++----------- ...w.js => WorkspaceCardVBAWithECardView.tsx} | 39 ++++++++------- 6 files changed, 117 insertions(+), 145 deletions(-) delete mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.js create mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.tsx delete mode 100644 src/pages/workspace/card/WorkspaceCardPage.js create mode 100644 src/pages/workspace/card/WorkspaceCardPage.tsx rename src/pages/workspace/card/{WorkspaceCardVBANoECardView.js => WorkspaceCardVBANoECardView.tsx} (53%) rename src/pages/workspace/card/{WorkspaceCardVBAWithECardView.js => WorkspaceCardVBAWithECardView.tsx} (67%) diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.js b/src/pages/workspace/card/WorkspaceCardNoVBAView.js deleted file mode 100644 index 3233f8ea7e23..000000000000 --- a/src/pages/workspace/card/WorkspaceCardNoVBAView.js +++ /dev/null @@ -1,49 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; -import * as Illustrations from '@components/Icon/Illustrations'; -import Section from '@components/Section'; -import Text from '@components/Text'; -import UnorderedList from '@components/UnorderedList'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; - -const propTypes = { - /** The policy ID currently being configured */ - policyID: PropTypes.string.isRequired, - - ...withLocalizePropTypes, -}; - -function WorkspaceCardNoVBAView(props) { - const styles = useThemeStyles(); - return ( -
- - {props.translate('workspace.card.noVBACopy')} - - - - -
- ); -} - -WorkspaceCardNoVBAView.propTypes = propTypes; -WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; - -export default withLocalize(WorkspaceCardNoVBAView); diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx b/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx new file mode 100644 index 000000000000..322d433a8e62 --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import {View} from 'react-native'; +import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; +import * as Illustrations from '@components/Icon/Illustrations'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import UnorderedList from '@components/UnorderedList'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type WorkspaceCardNoVBAViewProps = { + /** The policy ID currently being configured */ + policyID: string; +}; + +function WorkspaceCardNoVBAView({policyID}: WorkspaceCardNoVBAViewProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( +
+ + {translate('workspace.card.noVBACopy')} + + + + +
+ ); +} + +WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; + +export default WorkspaceCardNoVBAView; diff --git a/src/pages/workspace/card/WorkspaceCardPage.js b/src/pages/workspace/card/WorkspaceCardPage.js deleted file mode 100644 index 55220b85ce63..000000000000 --- a/src/pages/workspace/card/WorkspaceCardPage.js +++ /dev/null @@ -1,47 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import CONST from '@src/CONST'; -import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; -import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; -import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; - -const propTypes = { - /** The route object passed to this page from the navigator */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** The policyID that is being configured */ - policyID: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - - ...withLocalizePropTypes, -}; - -function WorkspaceCardPage(props) { - return ( - - {(hasVBA, policyID, isUsingECard) => ( - <> - {!hasVBA && } - - {hasVBA && !isUsingECard && } - - {hasVBA && isUsingECard && } - - )} - - ); -} - -WorkspaceCardPage.propTypes = propTypes; -WorkspaceCardPage.displayName = 'WorkspaceCardPage'; - -export default withLocalize(WorkspaceCardPage); diff --git a/src/pages/workspace/card/WorkspaceCardPage.tsx b/src/pages/workspace/card/WorkspaceCardPage.tsx new file mode 100644 index 000000000000..f6e368db84ea --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardPage.tsx @@ -0,0 +1,39 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; +import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; +import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; +import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; + +type WorkspaceCardPageProps = StackScreenProps; + +function WorkspaceCardPage({route}: WorkspaceCardPageProps) { + const {translate} = useLocalize(); + + return ( + + {(hasVBA: boolean, policyID: string, isUsingECard: boolean) => ( + <> + {false && } + + {false && } + + {true && } + + )} + + ); +} + +WorkspaceCardPage.displayName = 'WorkspaceCardPage'; + +export default WorkspaceCardPage; diff --git a/src/pages/workspace/card/WorkspaceCardVBANoECardView.js b/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx similarity index 53% rename from src/pages/workspace/card/WorkspaceCardVBANoECardView.js rename to src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx index 970cd9105368..3c9b773d6994 100644 --- a/src/pages/workspace/card/WorkspaceCardVBANoECardView.js +++ b/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -7,45 +8,37 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import userPropTypes from '@pages/settings/userPropTypes'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {User} from '@src/types/onyx'; -const propTypes = { +type WorkspaceCardVBANoECardViewOnyxProps = { /** Information about the logged in user's account */ - user: userPropTypes, - - ...withLocalizePropTypes, + user: OnyxEntry; }; -const defaultProps = { - user: {}, -}; +type WorkspaceCardVBANoECardViewProps = WorkspaceCardVBANoECardViewOnyxProps; -function WorkspaceCardVBANoECardView(props) { +function WorkspaceCardVBANoECardView({user}: WorkspaceCardVBANoECardViewProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); + return ( <>
- {Boolean(props.user.isCheckingDomain) && {props.translate('workspace.card.checkingDomain')}} + {Boolean(user?.isCheckingDomain) && {translate('workspace.card.checkingDomain')}} ); } -WorkspaceCardVBANoECardView.propTypes = propTypes; -WorkspaceCardVBANoECardView.defaultProps = defaultProps; WorkspaceCardVBANoECardView.displayName = 'WorkspaceCardVBANoECardView'; -export default compose( - withLocalize, - withOnyx({ - user: { - key: ONYXKEYS.USER, - }, - }), -)(WorkspaceCardVBANoECardView); +export default withOnyx({ + user: { + key: ONYXKEYS.USER, + }, +})(WorkspaceCardVBANoECardView); diff --git a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx similarity index 67% rename from src/pages/workspace/card/WorkspaceCardVBAWithECardView.js rename to src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx index 40ecd80b8e6e..a53a44fa52cf 100644 --- a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js +++ b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx @@ -2,28 +2,35 @@ import React from 'react'; import {View} from 'react-native'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; +import type {MenuItemWithLink} from '@components/MenuItemList'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Link from '@userActions/Link'; -const propTypes = { - ...withLocalizePropTypes, +type MenuLinks = { + ISSUE_AND_MANAGE_CARDS: string; + RECONCILE_CARDS: string; + SETTLEMENT_FREQUENCY: string; }; -const MENU_LINKS = { +type MenuItems = MenuItemWithLink[]; + +const MENU_LINKS: MenuLinks = { ISSUE_AND_MANAGE_CARDS: 'domain_companycards', RECONCILE_CARDS: encodeURI('domain_companycards?param={"section":"cardReconciliation"}'), SETTLEMENT_FREQUENCY: encodeURI('domain_companycards?param={"section":"configureSettings"}'), }; -function WorkspaceCardVBAWithECardView(props) { +function WorkspaceCardVBAWithECardView() { const styles = useThemeStyles(); - const menuItems = [ + const {translate} = useLocalize(); + + const menuItems: MenuItems = [ { - title: props.translate('workspace.common.issueAndManageCards'), + title: translate('workspace.common.issueAndManageCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), icon: Expensicons.ExpensifyCard, shouldShowRightIcon: true, @@ -32,7 +39,7 @@ function WorkspaceCardVBAWithECardView(props) { link: () => Link.buildOldDotURL(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), }, { - title: props.translate('workspace.common.reconcileCards'), + title: translate('workspace.common.reconcileCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.RECONCILE_CARDS), icon: Expensicons.ReceiptSearch, shouldShowRightIcon: true, @@ -41,7 +48,7 @@ function WorkspaceCardVBAWithECardView(props) { link: () => Link.buildOldDotURL(MENU_LINKS.RECONCILE_CARDS), }, { - title: props.translate('workspace.common.settlementFrequency'), + title: translate('workspace.common.settlementFrequency'), onPress: () => Link.openOldDotLink(MENU_LINKS.SETTLEMENT_FREQUENCY), icon: Expensicons.Gear, shouldShowRightIcon: true, @@ -53,29 +60,23 @@ function WorkspaceCardVBAWithECardView(props) { return (
- {props.translate('workspace.card.VBAWithECardCopy')} + {translate('workspace.card.VBAWithECardCopy')}
); } -WorkspaceCardVBAWithECardView.propTypes = propTypes; WorkspaceCardVBAWithECardView.displayName = 'WorkspaceCardVBAWithECardView'; -export default withLocalize(WorkspaceCardVBAWithECardView); +export default WorkspaceCardVBAWithECardView; From b9746297385965d1a1ba4150ab10e4110aef012d Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 1 Feb 2024 10:15:16 +0000 Subject: [PATCH 2/9] [TS migration] Migrate WorkspaceReimburse page --- src/ONYXKEYS.ts | 4 +- src/components/Picker/BasePicker.tsx | 4 +- src/components/Picker/types.ts | 2 +- src/libs/DistanceRequestUtils.ts | 2 +- src/libs/PolicyUtils.ts | 4 +- src/libs/actions/Policy.ts | 17 +- .../reimburse/WorkspaceRateAndUnitPage.js | 173 ------------------ .../reimburse/WorkspaceRateAndUnitPage.tsx | 157 ++++++++++++++++ .../reimburse/WorkspaceReimbursePage.js | 42 ----- .../reimburse/WorkspaceReimbursePage.tsx | 33 ++++ ...ction.js => WorkspaceReimburseSection.tsx} | 60 +++--- ...urseView.js => WorkspaceReimburseView.tsx} | 116 ++++-------- src/types/onyx/Form.ts | 9 +- src/types/onyx/Policy.ts | 6 +- src/types/onyx/index.ts | 3 +- 15 files changed, 291 insertions(+), 341 deletions(-) delete mode 100644 src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js create mode 100644 src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx delete mode 100644 src/pages/workspace/reimburse/WorkspaceReimbursePage.js create mode 100644 src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx rename src/pages/workspace/reimburse/{WorkspaceReimburseSection.js => WorkspaceReimburseSection.tsx} (57%) rename src/pages/workspace/reimburse/{WorkspaceReimburseView.js => WorkspaceReimburseView.tsx} (53%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2867cb3905a2..0eaa79d7cdda 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -486,8 +486,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM_DRAFT]: OnyxTypes.AddDebitCardForm; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM_DRAFT]: OnyxTypes.Form; - [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: OnyxTypes.Form; - [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT]: OnyxTypes.Form; + [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: OnyxTypes.RateUnitForm; + [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT]: OnyxTypes.RateUnitForm; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: OnyxTypes.Form; diff --git a/src/components/Picker/BasePicker.tsx b/src/components/Picker/BasePicker.tsx index 1bee95532104..020a3cf72680 100644 --- a/src/components/Picker/BasePicker.tsx +++ b/src/components/Picker/BasePicker.tsx @@ -69,11 +69,11 @@ function BasePicker( */ const onValueChange = (inputValue: TPickerValue, index: number) => { if (inputID) { - onInputChange(inputValue); + onInputChange?.(inputValue); return; } - onInputChange(inputValue, index); + onInputChange?.(inputValue, index); }; const enableHighlight = () => { diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts index edf39a59c9d8..6304b23e7a2c 100644 --- a/src/components/Picker/types.ts +++ b/src/components/Picker/types.ts @@ -73,7 +73,7 @@ type BasePickerProps = { shouldSaveDraft?: boolean; /** A callback method that is called when the value changes and it receives the selected value as an argument */ - onInputChange: (value: TPickerValue, index?: number) => void; + onInputChange?: (value: TPickerValue, index?: number) => void; /** Size of a picker component */ size?: PickerSize; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index c92e9bfd3f67..fb4e92d7f147 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -100,7 +100,7 @@ function getDistanceMerchant( const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; - const ratePerUnit = rate ? PolicyUtils.getUnitRateValue({rate}, toLocaleDigit) : translate('common.tbd'); + const ratePerUnit = rate ? PolicyUtils.getUnitRateValue(toLocaleDigit, {rate}) : translate('common.tbd'); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const currencySymbol = rate ? CurrencyUtils.getCurrencySymbol(currency) || `${currency} ` : ''; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index b8ed62f93082..a812cab24402 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,11 +4,11 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTag, PolicyTags} from '@src/types/onyx'; +import type {Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type MemberEmailsToAccountIDs = Record; -type UnitRate = {rate: number}; /** * Filter out the active policies, which will exclude policies with pending deletion @@ -66,7 +66,7 @@ function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => stri return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length); } -function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) => string) { +function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate?: Rate) { return getRateDisplayValue((customUnitRate?.rate ?? 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); } diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index fbe92aeb378d..48fd8944f196 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -67,6 +67,13 @@ type OptimisticCustomUnits = { outputCurrency: string; }; +type NewCustomUnit = { + customUnitID: string; + name: string; + attributes: Attributes; + rates: Rate; +}; + type PoliciesRecord = Record>; const allPolicies: OnyxCollection = {}; @@ -932,7 +939,7 @@ function hideWorkspaceAlertMessage(policyID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {alertMessage: ''}); } -function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: CustomUnit, lastModified: number) { +function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: NewCustomUnit, lastModified: number) { if (!currentCustomUnit.customUnitID || !newCustomUnit?.customUnitID || !newCustomUnit.rates?.customUnitRateID) { return; } @@ -946,7 +953,7 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C [newCustomUnit.customUnitID]: { ...newCustomUnit, rates: { - [newCustomUnit.rates.customUnitRateID as string]: { + [newCustomUnit.rates.customUnitRateID]: { ...newCustomUnit.rates, errors: null, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, @@ -969,7 +976,7 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C pendingAction: null, errors: null, rates: { - [newCustomUnit.rates.customUnitRateID as string]: { + [newCustomUnit.rates.customUnitRateID]: { pendingAction: null, }, }, @@ -988,7 +995,7 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C [currentCustomUnit.customUnitID]: { customUnitID: currentCustomUnit.customUnitID, rates: { - [newCustomUnit.rates.customUnitRateID as string]: { + [newCustomUnit.rates.customUnitRateID]: { ...currentCustomUnit.rates, errors: ErrorUtils.getMicroSecondOnyxError('workspace.reimburse.updateCustomUnitError'), }, @@ -2018,3 +2025,5 @@ export { createDraftInitialWorkspace, setWorkspaceInviteMessageDraft, }; + +export type {NewCustomUnit}; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js deleted file mode 100644 index 93ea7212e741..000000000000 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js +++ /dev/null @@ -1,173 +0,0 @@ -import lodashGet from 'lodash/get'; -import React, {useEffect} from 'react'; -import {Keyboard, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {withNetwork} from '@components/OnyxProvider'; -import Picker from '@components/Picker'; -import TextInput from '@components/TextInput'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; -import compose from '@libs/compose'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; -import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; -import Navigation from '@libs/Navigation/Navigation'; -import * as NumberUtils from '@libs/NumberUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import withPolicy, {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import * as BankAccounts from '@userActions/BankAccounts'; -import * as Policy from '@userActions/Policy'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -const propTypes = { - ...policyPropTypes, - ...withLocalizePropTypes, - ...withThemeStylesPropTypes, -}; - -const defaultProps = { - reimbursementAccount: {}, - ...policyDefaultProps, -}; - -function WorkspaceRateAndUnitPage(props) { - useEffect(() => { - if (lodashGet(props, 'policy.customUnits', []).length !== 0) { - return; - } - - BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(props.policy.id); - }, [props]); - - const unitItems = [ - {label: props.translate('common.kilometers'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}, - {label: props.translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}, - ]; - - const saveUnitAndRate = (unit, rate) => { - const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), (customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - if (!distanceCustomUnit) { - return; - } - const currentCustomUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - const unitID = lodashGet(distanceCustomUnit, 'customUnitID', ''); - const unitName = lodashGet(distanceCustomUnit, 'name', ''); - const rateNumValue = PolicyUtils.getNumericValue(rate, props.toLocaleDigit); - - const newCustomUnit = { - customUnitID: unitID, - name: unitName, - attributes: {unit}, - rates: { - ...currentCustomUnitRate, - rate: rateNumValue * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, - }, - }; - Policy.updateWorkspaceCustomUnitAndRate(props.policy.id, distanceCustomUnit, newCustomUnit, props.policy.lastModified); - }; - - const submit = (values) => { - saveUnitAndRate(values.unit, values.rate); - Keyboard.dismiss(); - Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy.id)); - }; - - const validate = (values) => { - const errors = {}; - const decimalSeparator = props.toLocaleDigit('.'); - const outputCurrency = lodashGet(props, 'policy.outputCurrency', CONST.CURRENCY.USD); - // Allow one more decimal place for accuracy - const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i'); - if (!rateValueRegex.test(values.rate) || values.rate === '') { - errors.rate = 'workspace.reimburse.invalidRateError'; - } else if (NumberUtils.parseFloatAnyLocale(values.rate) <= 0) { - errors.rate = 'workspace.reimburse.lowRateError'; - } - return errors; - }; - - const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), (unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - - return ( - - {() => ( - - - Policy.clearCustomUnitErrors(props.policy.id, lodashGet(distanceCustomUnit, 'customUnitID', ''), lodashGet(distanceCustomRate, 'customUnitRateID', '')) - } - > - - - - - - - - )} - - ); -} - -WorkspaceRateAndUnitPage.propTypes = propTypes; -WorkspaceRateAndUnitPage.defaultProps = defaultProps; -WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage'; - -export default compose( - withPolicy, - withLocalize, - withNetwork(), - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), - withThemeStyles, -)(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx new file mode 100644 index 000000000000..9c24b6bde023 --- /dev/null +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx @@ -0,0 +1,157 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useEffect} from 'react'; +import {Keyboard, View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {OnyxFormValuesFields} from '@components/Form/types'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import Picker from '@components/Picker'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as NumberUtils from '@libs/NumberUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import * as BankAccounts from '@userActions/BankAccounts'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {Unit} from '@src/types/onyx/Policy'; + +type WorkspaceRateAndUnitPageProps = WithPolicyProps & StackScreenProps; + +type ValidationError = {rate?: string}; + +function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps) { + const {translate, toLocaleDigit} = useLocalize(); + const styles = useThemeStyles(); + + useEffect(() => { + if ((policy?.customUnits ?? []).length !== 0) { + return; + } + + BankAccounts.setReimbursementAccountLoading(true); + Policy.openWorkspaceReimburseView(policy?.id ?? ''); + }, [policy?.customUnits, policy?.id]); + + const unitItems = [ + {label: translate('common.kilometers'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}, + {label: translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}, + ]; + + const saveUnitAndRate = (unit: Unit, rate: number) => { + const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceCustomUnit) { + return; + } + const currentCustomUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const unitID = distanceCustomUnit.customUnitID ?? ''; + const unitName = distanceCustomUnit.name ?? ''; + const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit) as number; + + const newCustomUnit: Policy.NewCustomUnit = { + customUnitID: unitID, + name: unitName, + attributes: {unit}, + rates: { + ...currentCustomUnitRate, + rate: rateNumValue * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, + }, + }; + + Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, parseInt(policy?.lastModified ?? '', 2)); + }; + + const submit = (values: OnyxFormValuesFields) => { + saveUnitAndRate(values.unit, values.rate); + Keyboard.dismiss(); + Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(policy?.id ?? '')); + }; + + const validate = (values: OnyxFormValuesFields): ValidationError => { + const errors: ValidationError = {}; + const decimalSeparator = toLocaleDigit('.'); + const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; + // Allow one more decimal place for accuracy + const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i'); + if (!rateValueRegex.test(values.rate.toString()) || values.rate.toString() === 'Nan') { + errors.rate = 'workspace.reimburse.invalidRateError'; + } else if (NumberUtils.parseFloatAnyLocale(values.rate.toString()) <= 0) { + errors.rate = 'workspace.reimburse.lowRateError'; + } + return errors; + }; + + const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + + return ( + + {() => ( + + Policy.clearCustomUnitErrors(policy?.id ?? '', distanceCustomUnit?.customUnitID ?? '', distanceCustomRate?.customUnitRateID ?? '')} + > + + + + + + + + )} + + ); +} + +WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage'; + +export default withPolicy(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js b/src/pages/workspace/reimburse/WorkspaceReimbursePage.js deleted file mode 100644 index fa3849abc941..000000000000 --- a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import compose from '@libs/compose'; -import withPolicy, {policyPropTypes} from '@pages/workspace/withPolicy'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import CONST from '@src/CONST'; -import WorkspaceReimburseView from './WorkspaceReimburseView'; - -const propTypes = { - /** The route object passed to this page from the navigator */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** The policyID that is being configured */ - policyID: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - - ...policyPropTypes, - ...withLocalizePropTypes, -}; - -function WorkspaceReimbursePage(props) { - return ( - - {() => } - - ); -} - -WorkspaceReimbursePage.propTypes = propTypes; -WorkspaceReimbursePage.displayName = 'WorkspaceReimbursePage'; - -export default compose(withPolicy, withLocalize)(WorkspaceReimbursePage); diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx new file mode 100644 index 000000000000..78bf58301db5 --- /dev/null +++ b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx @@ -0,0 +1,33 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import withPolicy from '@pages/workspace/withPolicy'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; +import WorkspaceReimburseView from './WorkspaceReimburseView'; + +type WorkspaceReimbursePageProps = WithPolicyProps & StackScreenProps; + +function WorkspaceReimbursePage({route, policy}: WorkspaceReimbursePageProps) { + const {translate} = useLocalize(); + + return ( + + {() => } + + ); +} + +WorkspaceReimbursePage.displayName = 'WorkspaceReimbursePage'; + +export default withPolicy(WorkspaceReimbursePage); diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx similarity index 57% rename from src/pages/workspace/reimburse/WorkspaceReimburseSection.js rename to src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx index 00ef284c50ae..e4c99d79e324 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx @@ -1,45 +1,40 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useEffect, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; -import networkPropTypes from '@components/networkPropTypes'; import Section from '@components/Section'; import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import BankAccount from '@libs/models/BankAccount'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; import * as Link from '@userActions/Link'; +import type * as OnyxTypes from '@src/types/onyx'; -const propTypes = { +type WorkspaceReimburseSectionProps = { /** Policy values needed in the component */ - policy: PropTypes.shape({ - id: PropTypes.string, - }).isRequired, + policy: OnyxEntry; /** Bank account attached to free plan */ - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - /** Returns translated string for given locale and phrase */ - translate: PropTypes.func.isRequired, + reimbursementAccount: OnyxEntry; }; -function WorkspaceReimburseSection(props) { +function WorkspaceReimburseSection({policy, reimbursementAccount}: WorkspaceReimburseSectionProps) { const theme = useTheme(); const styles = useThemeStyles(); + const {translate} = useLocalize(); const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(true); - const achState = lodashGet(props.reimbursementAccount, 'achData.state', ''); + const achState = reimbursementAccount?.achData?.state ?? ''; const hasVBA = achState === BankAccount.STATE.OPEN; - const reimburseReceiptsUrl = `reports?policyID=${props.policy.id}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`; - const isLoading = lodashGet(props.reimbursementAccount, 'isLoading', false); + const policyId = policy?.id ?? ''; + const reimburseReceiptsUrl = `reports?policyID=${policyId}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`; + const isLoading = reimbursementAccount?.isLoading ?? false; const prevIsLoading = usePrevious(isLoading); + const {isOffline} = useNetwork(); useEffect(() => { if (prevIsLoading === isLoading) { @@ -48,14 +43,14 @@ function WorkspaceReimburseSection(props) { setShouldShowLoadingSpinner(isLoading); }, [prevIsLoading, isLoading]); - if (props.network.isOffline) { + if (isOffline) { return (
- {`${props.translate('common.youAppearToBeOffline')} ${props.translate('common.thisFeatureRequiresInternet')}`} + {`${translate('common.youAppearToBeOffline')} ${translate('common.thisFeatureRequiresInternet')}`}
); @@ -76,35 +71,35 @@ function WorkspaceReimburseSection(props) { <> {hasVBA ? (
Link.openOldDotLink(reimburseReceiptsUrl), icon: Expensicons.Bank, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], + wrapperStyle: styles.cardMenuItem, link: () => Link.buildOldDotURL(reimburseReceiptsUrl), }, ]} > - - {props.translate('workspace.reimburse.fastReimbursementsVBACopy')} + + {translate('workspace.reimburse.fastReimbursementsVBACopy')}
) : (
- - {props.translate('workspace.reimburse.unlockNoVBACopy')} + + {translate('workspace.reimburse.unlockNoVBACopy')}
)} @@ -112,7 +107,6 @@ function WorkspaceReimburseSection(props) { ); } -WorkspaceReimburseSection.propTypes = propTypes; WorkspaceReimburseSection.displayName = 'WorkspaceReimburseSection'; export default WorkspaceReimburseSection; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx similarity index 53% rename from src/pages/workspace/reimburse/WorkspaceReimburseView.js rename to src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index 23136064fc2b..ea9ee9e8a421 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -1,85 +1,57 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import CopyTextToClipboard from '@components/CopyTextToClipboard'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import networkPropTypes from '@components/networkPropTypes'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {withNetwork} from '@components/OnyxProvider'; import Section from '@components/Section'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; import * as BankAccounts from '@userActions/BankAccounts'; import * as Link from '@userActions/Link'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; import WorkspaceReimburseSection from './WorkspaceReimburseSection'; -const propTypes = { - /** Policy values needed in the component */ - policy: PropTypes.shape({ - id: PropTypes.string, - customUnits: PropTypes.objectOf( - PropTypes.shape({ - customUnitID: PropTypes.string, - name: PropTypes.string, - attributes: PropTypes.shape({ - unit: PropTypes.string, - }), - rates: PropTypes.objectOf( - PropTypes.shape({ - customUnitRateID: PropTypes.string, - name: PropTypes.string, - rate: PropTypes.number, - }), - ), - }), - ), - outputCurrency: PropTypes.string, - lastModified: PropTypes.number, - }).isRequired, - +type WorkspaceReimburseViewOnyxProps = { /** From Onyx */ /** Bank account attached to free plan */ - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - ...withLocalizePropTypes, + reimbursementAccount: OnyxEntry; }; -const defaultProps = { - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, +type WorkspaceReimburseViewProps = WorkspaceReimburseViewOnyxProps & { + /** Policy values needed in the component */ + policy: OnyxEntry; }; -function WorkspaceReimburseView(props) { +function WorkspaceReimburseView({policy, reimbursementAccount}: WorkspaceReimburseViewProps) { const styles = useThemeStyles(); - const [currentRatePerUnit, setCurrentRatePerUnit] = useState(''); - const viewAllReceiptsUrl = `expenses?policyIDList=${props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; - const distanceCustomUnit = _.find(lodashGet(props.policy, 'customUnits', {}), (unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - const {translate, toLocaleDigit} = props; + const [currentRatePerUnit, setCurrentRatePerUnit] = useState(''); + const viewAllReceiptsUrl = `expenses?policyIDList=${policy?.id ?? ''}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; + const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const {translate, toLocaleDigit} = useLocalize(); + const {isOffline} = useNetwork(); - const getUnitLabel = useCallback((value) => translate(`common.${value}`), [translate]); + const getUnitLabel = useCallback((value: Unit): string => translate(`common.${value}`), [translate]); const getCurrentRatePerUnitLabel = useCallback(() => { - const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', '{}'), (rate) => rate && rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - const currentUnit = getUnitLabel(lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES)); - const currentRate = PolicyUtils.getUnitRateValue(customUnitRate, toLocaleDigit); + const customUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate && rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const currentUnit = getUnitLabel(distanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + const currentRate = PolicyUtils.getUnitRateValue(toLocaleDigit, customUnitRate); const perWord = translate('common.per'); + return `${currentRate} ${perWord} ${currentUnit}`; }, [translate, distanceCustomUnit, toLocaleDigit, getUnitLabel]); @@ -88,19 +60,19 @@ function WorkspaceReimburseView(props) { // openWorkspaceReimburseView uses API.read which will not make the request until all WRITE requests in the sequential queue have finished responding, so there would be a delay in // updating Onyx with the optimistic data. BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(props.policy.id); - }, [props.policy.id]); + Policy.openWorkspaceReimburseView(policy?.id ?? ''); + }, [policy?.id]); useEffect(() => { - if (props.network.isOffline) { + if (isOffline) { return; } fetchData(); - }, [props.network.isOffline, fetchData]); + }, [isOffline, fetchData]); useEffect(() => { setCurrentRatePerUnit(getCurrentRatePerUnitLabel()); - }, [props.policy.customUnits, getCurrentRatePerUnitLabel]); + }, [policy?.customUnits, getCurrentRatePerUnitLabel]); return ( <> @@ -114,7 +86,7 @@ function WorkspaceReimburseView(props) { icon: Expensicons.Receipt, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], + wrapperStyle: styles.cardMenuItem, link: () => Link.buildOldDotURL(viewAllReceiptsUrl), }, ]} @@ -124,7 +96,7 @@ function WorkspaceReimburseView(props) { {translate('workspace.reimburse.captureNoVBACopyBeforeEmail')} {translate('workspace.reimburse.captureNoVBACopyAfterEmail')} @@ -135,44 +107,36 @@ function WorkspaceReimburseView(props) { title={translate('workspace.reimburse.trackDistance')} icon={Illustrations.TrackShoe} > - + {translate('workspace.reimburse.trackDistanceCopy')} Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy.id))} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(policy?.id ?? ''))} wrapperStyle={[styles.mhn5, styles.wAuto]} - brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} + brickRoadIndicator={(distanceCustomUnit?.errors ?? distanceCustomRate?.errors) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} /> ); } -WorkspaceReimburseView.defaultProps = defaultProps; -WorkspaceReimburseView.propTypes = propTypes; WorkspaceReimburseView.displayName = 'WorkspaceReimburseView'; -export default compose( - withLocalize, - withNetwork(), - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), -)(WorkspaceReimburseView); +export default withOnyx({ + reimbursementAccount: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + }, +})(WorkspaceReimburseView); diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index 9c6d52a1020d..5c0bb096b42f 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -1,7 +1,8 @@ import type * as OnyxCommon from './OnyxCommon'; import type PersonalBankAccount from './PersonalBankAccount'; +import type {Unit} from './Policy'; -type FormValueType = string | boolean | Date | OnyxCommon.Errors; +type FormValueType = string | boolean | Date | number | OnyxCommon.Errors; type BaseForm = { /** Controls the loading state of the form */ @@ -59,6 +60,11 @@ type PersonalBankAccountForm = Form; type ReportFieldEditForm = Form>; +type RateUnitForm = Form<{ + unit: Unit; + rate: number; +}>; + export default Form; export type { @@ -73,4 +79,5 @@ export type { IntroSchoolPrincipalForm, PersonalBankAccountForm, ReportFieldEditForm, + RateUnitForm, }; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index fe50bbb497d2..ecc8b1797654 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -10,7 +10,7 @@ type Rate = { currency?: string; customUnitRateID?: string; errors?: OnyxCommon.Errors; - pendingAction?: string; + pendingAction?: OnyxCommon.PendingAction; }; type Attributes = { @@ -22,7 +22,7 @@ type CustomUnit = { customUnitID: string; attributes: Attributes; rates: Record; - pendingAction?: string; + pendingAction?: OnyxCommon.PendingAction; errors?: OnyxCommon.Errors; }; @@ -156,4 +156,4 @@ type Policy = { export default Policy; -export type {Unit, CustomUnit}; +export type {Unit, CustomUnit, Rate, Attributes}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 64eec736b5bf..a2cc5a878b47 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -9,7 +9,7 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; import type Download from './Download'; -import type {AddDebitCardForm, DateOfBirthForm, DisplayNameForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, PrivateNotesForm, ReportFieldEditForm} from './Form'; +import type {AddDebitCardForm, DateOfBirthForm, DisplayNameForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, PrivateNotesForm, RateUnitForm, ReportFieldEditForm} from './Form'; import type Form from './Form'; import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; @@ -151,5 +151,6 @@ export type { IKnowATeacherForm, IntroSchoolPrincipalForm, PrivateNotesForm, + RateUnitForm, ReportFieldEditForm, }; From f51dfad8cf298537861d378a9336e4598115ce5a Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 1 Feb 2024 10:15:28 +0000 Subject: [PATCH 3/9] Revert "[TS Migration] Migrate WorkspaceCard to Typescript" This reverts commit 4feb09259b4501462807da3bb197aa705e3c1d4f. --- .../workspace/card/WorkspaceCardNoVBAView.js | 49 +++++++++++++++++++ .../workspace/card/WorkspaceCardNoVBAView.tsx | 40 --------------- src/pages/workspace/card/WorkspaceCardPage.js | 47 ++++++++++++++++++ .../workspace/card/WorkspaceCardPage.tsx | 39 --------------- ...iew.tsx => WorkspaceCardVBANoECardView.js} | 48 +++++++++++------- ...w.tsx => WorkspaceCardVBAWithECardView.js} | 39 +++++++-------- 6 files changed, 145 insertions(+), 117 deletions(-) create mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.js delete mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.tsx create mode 100644 src/pages/workspace/card/WorkspaceCardPage.js delete mode 100644 src/pages/workspace/card/WorkspaceCardPage.tsx rename src/pages/workspace/card/{WorkspaceCardVBANoECardView.tsx => WorkspaceCardVBANoECardView.js} (53%) rename src/pages/workspace/card/{WorkspaceCardVBAWithECardView.tsx => WorkspaceCardVBAWithECardView.js} (67%) diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.js b/src/pages/workspace/card/WorkspaceCardNoVBAView.js new file mode 100644 index 000000000000..3233f8ea7e23 --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardNoVBAView.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import {View} from 'react-native'; +import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; +import * as Illustrations from '@components/Icon/Illustrations'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import UnorderedList from '@components/UnorderedList'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +const propTypes = { + /** The policy ID currently being configured */ + policyID: PropTypes.string.isRequired, + + ...withLocalizePropTypes, +}; + +function WorkspaceCardNoVBAView(props) { + const styles = useThemeStyles(); + return ( +
+ + {props.translate('workspace.card.noVBACopy')} + + + + +
+ ); +} + +WorkspaceCardNoVBAView.propTypes = propTypes; +WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; + +export default withLocalize(WorkspaceCardNoVBAView); diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx b/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx deleted file mode 100644 index 322d433a8e62..000000000000 --- a/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; -import * as Illustrations from '@components/Icon/Illustrations'; -import Section from '@components/Section'; -import Text from '@components/Text'; -import UnorderedList from '@components/UnorderedList'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; - -type WorkspaceCardNoVBAViewProps = { - /** The policy ID currently being configured */ - policyID: string; -}; - -function WorkspaceCardNoVBAView({policyID}: WorkspaceCardNoVBAViewProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - return ( -
- - {translate('workspace.card.noVBACopy')} - - - - -
- ); -} - -WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; - -export default WorkspaceCardNoVBAView; diff --git a/src/pages/workspace/card/WorkspaceCardPage.js b/src/pages/workspace/card/WorkspaceCardPage.js new file mode 100644 index 000000000000..55220b85ce63 --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardPage.js @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; +import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; +import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; + +const propTypes = { + /** The route object passed to this page from the navigator */ + route: PropTypes.shape({ + /** Each parameter passed via the URL */ + params: PropTypes.shape({ + /** The policyID that is being configured */ + policyID: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + + ...withLocalizePropTypes, +}; + +function WorkspaceCardPage(props) { + return ( + + {(hasVBA, policyID, isUsingECard) => ( + <> + {!hasVBA && } + + {hasVBA && !isUsingECard && } + + {hasVBA && isUsingECard && } + + )} + + ); +} + +WorkspaceCardPage.propTypes = propTypes; +WorkspaceCardPage.displayName = 'WorkspaceCardPage'; + +export default withLocalize(WorkspaceCardPage); diff --git a/src/pages/workspace/card/WorkspaceCardPage.tsx b/src/pages/workspace/card/WorkspaceCardPage.tsx deleted file mode 100644 index f6e368db84ea..000000000000 --- a/src/pages/workspace/card/WorkspaceCardPage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; -import useLocalize from '@hooks/useLocalize'; -import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import CONST from '@src/CONST'; -import type SCREENS from '@src/SCREENS'; -import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; -import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; -import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; - -type WorkspaceCardPageProps = StackScreenProps; - -function WorkspaceCardPage({route}: WorkspaceCardPageProps) { - const {translate} = useLocalize(); - - return ( - - {(hasVBA: boolean, policyID: string, isUsingECard: boolean) => ( - <> - {false && } - - {false && } - - {true && } - - )} - - ); -} - -WorkspaceCardPage.displayName = 'WorkspaceCardPage'; - -export default WorkspaceCardPage; diff --git a/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx b/src/pages/workspace/card/WorkspaceCardVBANoECardView.js similarity index 53% rename from src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx rename to src/pages/workspace/card/WorkspaceCardVBANoECardView.js index 3c9b773d6994..970cd9105368 100644 --- a/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx +++ b/src/pages/workspace/card/WorkspaceCardVBANoECardView.js @@ -1,6 +1,5 @@ import React from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -8,37 +7,45 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import useLocalize from '@hooks/useLocalize'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import compose from '@libs/compose'; +import userPropTypes from '@pages/settings/userPropTypes'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {User} from '@src/types/onyx'; -type WorkspaceCardVBANoECardViewOnyxProps = { +const propTypes = { /** Information about the logged in user's account */ - user: OnyxEntry; + user: userPropTypes, + + ...withLocalizePropTypes, }; -type WorkspaceCardVBANoECardViewProps = WorkspaceCardVBANoECardViewOnyxProps; +const defaultProps = { + user: {}, +}; -function WorkspaceCardVBANoECardView({user}: WorkspaceCardVBANoECardViewProps) { +function WorkspaceCardVBANoECardView(props) { const styles = useThemeStyles(); - const {translate} = useLocalize(); - return ( <>
- {Boolean(user?.isCheckingDomain) && {translate('workspace.card.checkingDomain')}} + {Boolean(props.user.isCheckingDomain) && {props.translate('workspace.card.checkingDomain')}} ); } +WorkspaceCardVBANoECardView.propTypes = propTypes; +WorkspaceCardVBANoECardView.defaultProps = defaultProps; WorkspaceCardVBANoECardView.displayName = 'WorkspaceCardVBANoECardView'; -export default withOnyx({ - user: { - key: ONYXKEYS.USER, - }, -})(WorkspaceCardVBANoECardView); +export default compose( + withLocalize, + withOnyx({ + user: { + key: ONYXKEYS.USER, + }, + }), +)(WorkspaceCardVBANoECardView); diff --git a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js similarity index 67% rename from src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx rename to src/pages/workspace/card/WorkspaceCardVBAWithECardView.js index a53a44fa52cf..40ecd80b8e6e 100644 --- a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx +++ b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js @@ -2,35 +2,28 @@ import React from 'react'; import {View} from 'react-native'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; -import type {MenuItemWithLink} from '@components/MenuItemList'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import useLocalize from '@hooks/useLocalize'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Link from '@userActions/Link'; -type MenuLinks = { - ISSUE_AND_MANAGE_CARDS: string; - RECONCILE_CARDS: string; - SETTLEMENT_FREQUENCY: string; +const propTypes = { + ...withLocalizePropTypes, }; -type MenuItems = MenuItemWithLink[]; - -const MENU_LINKS: MenuLinks = { +const MENU_LINKS = { ISSUE_AND_MANAGE_CARDS: 'domain_companycards', RECONCILE_CARDS: encodeURI('domain_companycards?param={"section":"cardReconciliation"}'), SETTLEMENT_FREQUENCY: encodeURI('domain_companycards?param={"section":"configureSettings"}'), }; -function WorkspaceCardVBAWithECardView() { +function WorkspaceCardVBAWithECardView(props) { const styles = useThemeStyles(); - const {translate} = useLocalize(); - - const menuItems: MenuItems = [ + const menuItems = [ { - title: translate('workspace.common.issueAndManageCards'), + title: props.translate('workspace.common.issueAndManageCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), icon: Expensicons.ExpensifyCard, shouldShowRightIcon: true, @@ -39,7 +32,7 @@ function WorkspaceCardVBAWithECardView() { link: () => Link.buildOldDotURL(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), }, { - title: translate('workspace.common.reconcileCards'), + title: props.translate('workspace.common.reconcileCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.RECONCILE_CARDS), icon: Expensicons.ReceiptSearch, shouldShowRightIcon: true, @@ -48,7 +41,7 @@ function WorkspaceCardVBAWithECardView() { link: () => Link.buildOldDotURL(MENU_LINKS.RECONCILE_CARDS), }, { - title: translate('workspace.common.settlementFrequency'), + title: props.translate('workspace.common.settlementFrequency'), onPress: () => Link.openOldDotLink(MENU_LINKS.SETTLEMENT_FREQUENCY), icon: Expensicons.Gear, shouldShowRightIcon: true, @@ -60,23 +53,29 @@ function WorkspaceCardVBAWithECardView() { return (
- {translate('workspace.card.VBAWithECardCopy')} + {props.translate('workspace.card.VBAWithECardCopy')}
); } +WorkspaceCardVBAWithECardView.propTypes = propTypes; WorkspaceCardVBAWithECardView.displayName = 'WorkspaceCardVBAWithECardView'; -export default WorkspaceCardVBAWithECardView; +export default withLocalize(WorkspaceCardVBAWithECardView); From 3a7ad1e1314b598cd5acdd012ac15ba4890a2112 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 1 Feb 2024 10:20:07 +0000 Subject: [PATCH 4/9] [TS migration][WorkspaceReimburse] Missing imports --- src/libs/actions/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 48fd8944f196..c8f982a0c6c6 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -37,7 +37,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; -import type {CustomUnit} from '@src/types/onyx/Policy'; +import type {Attributes, CustomUnit, Rate} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AnnounceRoomMembersOnyxData = { From d11c98a08b95289c93bf2824f9aff85ee9741c1d Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Fri, 2 Feb 2024 09:39:11 +0000 Subject: [PATCH 5/9] [TS migration][WorkspaceReimburse] Code improvements --- .../parameters/UpdateWorkspaceCustomUnitAndRateParams.ts | 2 +- src/libs/PolicyUtils.ts | 2 +- src/libs/actions/Policy.ts | 2 +- .../workspace/reimburse/WorkspaceRateAndUnitPage.tsx | 8 ++++---- src/pages/workspace/reimburse/WorkspaceReimburseView.tsx | 1 - src/types/onyx/Form.ts | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts index 22bbd20c7308..02186cddd32a 100644 --- a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts +++ b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts @@ -1,6 +1,6 @@ type UpdateWorkspaceCustomUnitAndRateParams = { policyID: string; - lastModified: number; + lastModified?: number; customUnit: string; customUnitRate: string; }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index a812cab24402..b4270867f9b2 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -50,7 +50,7 @@ function hasCustomUnitsError(policy: OnyxEntry): boolean { return Object.keys(policy?.customUnits?.errors ?? {}).length > 0; } -function getNumericValue(value: number, toLocaleDigit: (arg: string) => string): number | string { +function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => string): number | string { const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); if (Number.isNaN(numValue)) { return NaN; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index c8f982a0c6c6..1c9295dc08bc 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -939,7 +939,7 @@ function hideWorkspaceAlertMessage(policyID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {alertMessage: ''}); } -function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: NewCustomUnit, lastModified: number) { +function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: NewCustomUnit, lastModified?: string) { if (!currentCustomUnit.customUnitID || !newCustomUnit?.customUnitID || !newCustomUnit.rates?.customUnitRateID) { return; } diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx index 9c24b6bde023..1f5d7d854046 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx @@ -48,7 +48,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps {label: translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}, ]; - const saveUnitAndRate = (unit: Unit, rate: number) => { + const saveUnitAndRate = (unit: Unit, rate: string) => { const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); if (!distanceCustomUnit) { return; @@ -68,7 +68,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps }, }; - Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, parseInt(policy?.lastModified ?? '', 2)); + Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, policy?.lastModified); }; const submit = (values: OnyxFormValuesFields) => { @@ -83,9 +83,9 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; // Allow one more decimal place for accuracy const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i'); - if (!rateValueRegex.test(values.rate.toString()) || values.rate.toString() === 'Nan') { + if (!rateValueRegex.test(values.rate) || values.rate === '') { errors.rate = 'workspace.reimburse.invalidRateError'; - } else if (NumberUtils.parseFloatAnyLocale(values.rate.toString()) <= 0) { + } else if (NumberUtils.parseFloatAnyLocale(values.rate) <= 0) { errors.rate = 'workspace.reimburse.lowRateError'; } return errors; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index ea9ee9e8a421..375fa5b8d439 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -25,7 +25,6 @@ import type {Unit} from '@src/types/onyx/Policy'; import WorkspaceReimburseSection from './WorkspaceReimburseSection'; type WorkspaceReimburseViewOnyxProps = { - /** From Onyx */ /** Bank account attached to free plan */ reimbursementAccount: OnyxEntry; }; diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index 5c0bb096b42f..1b119460b344 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -62,7 +62,7 @@ type ReportFieldEditForm = Form>; type RateUnitForm = Form<{ unit: Unit; - rate: number; + rate: string; }>; export default Form; From 34135a2c023764b456df2fe86a0c95af2cb26022 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 8 Feb 2024 15:54:07 +0000 Subject: [PATCH 6/9] [TS migration][WorkspaceReimburse] Code improvements --- .../UpdateWorkspaceCustomUnitAndRateParams.ts | 2 +- .../reimburse/WorkspaceRateAndUnitPage.tsx | 4 ++-- .../workspace/reimburse/WorkspaceReimburseView.tsx | 13 ------------- src/types/onyx/Form.ts | 2 +- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts index a94089512a36..010bcaa1e60a 100644 --- a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts +++ b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts @@ -1,6 +1,6 @@ type UpdateWorkspaceCustomUnitAndRateParams = { policyID: string; - lastModified?: number | string; + lastModified?: string; customUnit: string; customUnitRate: string; }; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx index 1f5d7d854046..6011c303f535 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx @@ -56,7 +56,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps const currentCustomUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const unitID = distanceCustomUnit.customUnitID ?? ''; const unitName = distanceCustomUnit.name ?? ''; - const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit) as number; + const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit); const newCustomUnit: Policy.NewCustomUnit = { customUnitID: unitID, @@ -64,7 +64,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps attributes: {unit}, rates: { ...currentCustomUnitRate, - rate: rateNumValue * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, + rate: Number(rateNumValue) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, }, }; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index a61313b8b1e3..cb0b5bce7fc3 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -127,19 +127,6 @@ function WorkspaceReimburseView({policy, reimbursementAccount}: WorkspaceReimbur />
- - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(policy?.id ?? ''))} - wrapperStyle={[styles.mhn5, styles.wAuto]} - brickRoadIndicator={(distanceCustomUnit?.errors ?? distanceCustomRate?.errors) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} - /> - Date: Tue, 27 Feb 2024 09:57:32 +0000 Subject: [PATCH 7/9] [TS migration][WorkspaceReimburse] Ts issues fix --- src/libs/PolicyUtils.ts | 1 + .../workspace/reimburse/WorkspaceRateAndUnitPage.tsx | 11 ++++++----- .../workspace/reimburse/WorkspaceReimburseView.tsx | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 650f290ed065..b5b9e0eb5848 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,6 +4,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx'; +import type {Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx index 6011c303f535..8fef5f4dc6f9 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx @@ -3,7 +3,7 @@ import React, {useEffect} from 'react'; import {Keyboard, View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {OnyxFormValuesFields} from '@components/Form/types'; +import type {FormOnyxValues} from '@components/Form/types'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Picker from '@components/Picker'; import TextInput from '@components/TextInput'; @@ -21,6 +21,7 @@ import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSection import * as BankAccounts from '@userActions/BankAccounts'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -28,7 +29,7 @@ import type {Unit} from '@src/types/onyx/Policy'; type WorkspaceRateAndUnitPageProps = WithPolicyProps & StackScreenProps; -type ValidationError = {rate?: string}; +type ValidationError = {rate?: TranslationPaths | undefined}; function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps) { const {translate, toLocaleDigit} = useLocalize(); @@ -71,13 +72,13 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, policy?.lastModified); }; - const submit = (values: OnyxFormValuesFields) => { - saveUnitAndRate(values.unit, values.rate); + const submit = (values: FormOnyxValues) => { + saveUnitAndRate(values.unit as Unit, values.rate); Keyboard.dismiss(); Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(policy?.id ?? '')); }; - const validate = (values: OnyxFormValuesFields): ValidationError => { + const validate = (values: FormOnyxValues): ValidationError => { const errors: ValidationError = {}; const decimalSeparator = toLocaleDigit('.'); const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index b1a0e50cdca7..050398d00235 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -143,6 +143,7 @@ function WorkspaceReimburseView({policy, reimbursementAccount}: WorkspaceReimbur WorkspaceReimburseView.displayName = 'WorkspaceReimburseView'; export default withOnyx({ + // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, From aafd0e5b047113afaecaf5cc5c04e766c1f97e57 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 27 Feb 2024 12:40:29 +0000 Subject: [PATCH 8/9] [TS migration][WorkspaceReimburse] Lint --- src/pages/workspace/reimburse/WorkspaceReimburseView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index 050398d00235..9c7b1c493e8d 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -143,7 +143,7 @@ function WorkspaceReimburseView({policy, reimbursementAccount}: WorkspaceReimbur WorkspaceReimburseView.displayName = 'WorkspaceReimburseView'; export default withOnyx({ - // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM + // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, From 64cfc7eca8d2d256a89437b550c2e6d19b94efee Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 21 Mar 2024 12:51:19 +0000 Subject: [PATCH 9/9] [TS migration][WorkspaceReimburse] TS issue fix --- src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx index bdd85d736ace..3ca99147a83b 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx @@ -1,7 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import useLocalize from '@hooks/useLocalize'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import withPolicy from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; @@ -9,7 +9,7 @@ import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; import WorkspaceReimburseView from './WorkspaceReimburseView'; -type WorkspaceReimbursePageProps = WithPolicyProps & StackScreenProps; +type WorkspaceReimbursePageProps = WithPolicyProps & StackScreenProps; function WorkspaceReimbursePage({route, policy}: WorkspaceReimbursePageProps) { const {translate} = useLocalize();