From c7be0e6eac513a9cff3412ffe6b4c37c7796012a Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 20 Jan 2024 17:47:38 +0530 Subject: [PATCH 01/35] WIP: test form flow --- src/SCREENS.ts | 4 ++- src/components/FormMenuItem.tsx | 29 +++++++++++++++++ src/components/MenuItem.tsx | 6 ++-- .../AppNavigator/ModalStackNavigators.tsx | 2 +- src/libs/Navigation/linkingConfig.ts | 2 +- src/libs/Navigation/types.ts | 2 +- .../reimburse/WorkspaceRateAndUnitPage.js | 32 +++++++++++++------ 7 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 src/components/FormMenuItem.tsx diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 703cb309d641..84d96473fbe6 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -195,7 +195,9 @@ const SCREENS = { SETTINGS: 'Workspace_Settings', CARD: 'Workspace_Card', REIMBURSE: 'Workspace_Reimburse', - RATE_AND_UNIT: 'Workspace_RateAndUnit', + RATE_AND_UNIT: { + ROOT: 'Workspace_RateAndUnit', + }, BILLS: 'Workspace_Bills', INVOICES: 'Workspace_Invoices', TRAVEL: 'Workspace_Travel', diff --git a/src/components/FormMenuItem.tsx b/src/components/FormMenuItem.tsx new file mode 100644 index 000000000000..0203643c4df9 --- /dev/null +++ b/src/components/FormMenuItem.tsx @@ -0,0 +1,29 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import type {View} from 'react-native'; +import type {AvatarProps, IconProps, MenuItemBaseProps, NoIcon} from './MenuItem'; +import MenuItem from './MenuItem'; + +type FormMenuItemProps = (NoIcon | AvatarProps | IconProps) & + Omit & { + /** A description text to show under the title provided by the FormProvider */ + value?: string; + }; + +function FormMenuItem({value, errorText, ...props}: FormMenuItemProps, ref: ForwardedRef) { + return ( + + ); +} + +FormMenuItem.displayName = 'FormMenuItem'; + +export type {FormMenuItemProps}; +export default forwardRef(FormMenuItem); diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 334fa9895205..3c8d2fe37223 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -55,7 +55,7 @@ type NoIcon = { icon?: undefined; }; -type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { +type MenuItemBaseProps = { /** Function to fire when component is pressed */ onPress?: (event: GestureResponderEvent | KeyboardEvent) => void; @@ -225,6 +225,8 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { contentFit?: ImageContentFit; }; +type MenuItemProps = (IconProps | AvatarProps | NoIcon) & MenuItemBaseProps; + function MenuItem( { interactive = true, @@ -604,5 +606,5 @@ function MenuItem( MenuItem.displayName = 'MenuItem'; -export type {MenuItemProps}; +export type {IconProps, AvatarProps, NoIcon, MenuItemBaseProps, MenuItemProps}; export default forwardRef(MenuItem); diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index b0f33af0ce2e..e2aff7243d1a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -229,7 +229,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceSettingsCurrencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.CARD]: () => require('../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType, [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType, - [SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage').default as React.ComponentType, [SCREENS.WORKSPACE.BILLS]: () => require('../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType, [SCREENS.WORKSPACE.INVOICES]: () => require('../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType, [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index 1a495e92eb80..d13f81635277 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -243,7 +243,7 @@ const linkingConfig: LinkingOptions = { [SCREENS.WORKSPACE.REIMBURSE]: { path: ROUTES.WORKSPACE_REIMBURSE.route, }, - [SCREENS.WORKSPACE.RATE_AND_UNIT]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: { path: ROUTES.WORKSPACE_RATE_AND_UNIT.route, }, [SCREENS.WORKSPACE.BILLS]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index a1cbb562997e..2bd47f907e02 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -95,7 +95,7 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.REIMBURSE]: { policyID: string; }; - [SCREENS.WORKSPACE.RATE_AND_UNIT]: undefined; + [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: undefined; [SCREENS.WORKSPACE.BILLS]: { policyID: string; }; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js index 93ea7212e741..69753f9bdccb 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js @@ -1,14 +1,15 @@ import lodashGet from 'lodash/get'; import React, {useEffect} from 'react'; -import {Keyboard, View} from 'react-native'; +// import {Keyboard, View} from 'react-native'; +import {Keyboard} 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 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'; @@ -24,6 +25,7 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import FormMenuItem from '@components/FormMenuItem'; const propTypes = { ...policyPropTypes, @@ -46,10 +48,10 @@ function WorkspaceRateAndUnitPage(props) { 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 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); @@ -125,7 +127,7 @@ function WorkspaceRateAndUnitPage(props) { Policy.clearCustomUnitErrors(props.policy.id, lodashGet(distanceCustomUnit, 'customUnitID', ''), lodashGet(distanceCustomRate, 'customUnitRateID', '')) } > - - + */} + + )} From aaa03ec95885f67eb0e04a498d3bd84ff512e183 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 20 Jan 2024 18:35:28 +0530 Subject: [PATCH 02/35] WIP: minor visual fixes --- src/components/FormMenuItem.tsx | 7 +++++-- src/libs/CurrencyUtils.ts | 21 +++++++++++++++++++ .../reimburse/WorkspaceRateAndUnitPage.js | 14 ++++++++----- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/components/FormMenuItem.tsx b/src/components/FormMenuItem.tsx index 0203643c4df9..8455763230ff 100644 --- a/src/components/FormMenuItem.tsx +++ b/src/components/FormMenuItem.tsx @@ -8,15 +8,18 @@ type FormMenuItemProps = (NoIcon | AvatarProps | IconProps) & Omit & { /** A description text to show under the title provided by the FormProvider */ value?: string; + + /** Custom value renderer to render description based on form values */ + customValueRenderer?: (value?: string) => (string | undefined); }; -function FormMenuItem({value, errorText, ...props}: FormMenuItemProps, ref: ForwardedRef) { +function FormMenuItem({customValueRenderer, value, errorText, ...props}: FormMenuItemProps, ref: ForwardedRef) { return ( diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 42387e03c80b..55b16039cf17 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -116,6 +116,26 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR }); } +/** + * Given an amount, convert it to a string for display in the UI. + * + * @param amount – should be a float. + * @param currency - IOU currency + * @param shouldFallbackToTbd - whether to return 'TBD' instead of a falsy value (e.g. 0.00) + */ +function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRENCY.USD, shouldFallbackToTbd = false): string { + if (shouldFallbackToTbd && !amount) { + return Localize.translateLocal('common.tbd'); + } + + const convertedAmount = amount / 100.0; + return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { + style: 'currency', + currency, + minimumFractionDigits: getCurrencyDecimals(currency) + 1, + }); +} + /** * Checks if passed currency code is a valid currency based on currency list */ @@ -133,5 +153,6 @@ export { convertToBackendAmount, convertToFrontendAmount, convertToDisplayString, + convertAmountToDisplayString, isValidCurrencyCode, }; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js index 69753f9bdccb..614255af4497 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js @@ -48,10 +48,10 @@ function WorkspaceRateAndUnitPage(props) { 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 unitItems = { + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: props.translate('common.kilometers'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: props.translate('common.miles'), + }; const saveUnitAndRate = (unit, rate) => { const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), (customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); @@ -154,14 +154,18 @@ function WorkspaceRateAndUnitPage(props) { CurrencyUtils.convertAmountToDisplayString(value, lodashGet(props, 'policy.outputCurrency', CONST.CURRENCY.USD))} + shouldShowRightIcon /> unitItems[value]} + shouldShowRightIcon /> From 7ae7b0a59ff6e828ef819a485c4d17397e1d043a Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 21 Jan 2024 19:22:35 +0530 Subject: [PATCH 03/35] WIP: adding unit page, and amountForm component --- src/ROUTES.ts | 8 + src/SCREENS.ts | 4 +- src/components/AmountForm.tsx | 251 ++++++++++++++++++ src/components/BigNumberPad.tsx | 2 +- .../AppNavigator/ModalStackNavigators.tsx | 4 +- src/libs/Navigation/linkingConfig.ts | 6 + src/libs/Navigation/types.ts | 2 + src/libs/actions/Policy.ts | 3 + .../BasePage.js} | 14 +- .../WorkspaceRateAndUnitPage/RatePage.tsx | 0 .../WorkspaceRateAndUnitPage/UnitPage.tsx | 98 +++++++ 11 files changed, 385 insertions(+), 7 deletions(-) create mode 100644 src/components/AmountForm.tsx rename src/pages/workspace/reimburse/{WorkspaceRateAndUnitPage.js => WorkspaceRateAndUnitPage/BasePage.js} (95%) create mode 100644 src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx create mode 100644 src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 37003a09a0cd..613d781eeb44 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -450,6 +450,14 @@ const ROUTES = { route: 'workspace/:policyID/rateandunit', getRoute: (policyID: string) => `workspace/${policyID}/rateandunit` as const, }, + WORKSPACE_RATE_AND_UNIT_RATE: { + route: 'workspace/:policyID/rateandunit/rate', + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/rate` as const, + }, + WORKSPACE_RATE_AND_UNIT_UNIT: { + route: 'workspace/:policyID/rateandunit/unit', + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/unit` as const, + }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', getRoute: (policyID: string) => `workspace/${policyID}/bills` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 84d96473fbe6..e058081a41ba 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -196,7 +196,9 @@ const SCREENS = { CARD: 'Workspace_Card', REIMBURSE: 'Workspace_Reimburse', RATE_AND_UNIT: { - ROOT: 'Workspace_RateAndUnit', + ROOT: 'Workspace_RateAndUnit_Root', + RATE: 'Workspace_RateAndUnit_Rate', + UNIT: 'Workspace_RateAndUnit_Unit', }, BILLS: 'Workspace_Bills', INVOICES: 'Workspace_Invoices', diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx new file mode 100644 index 000000000000..848b738815f2 --- /dev/null +++ b/src/components/AmountForm.tsx @@ -0,0 +1,251 @@ +import type {ForwardedRef} from 'react'; +import React, {useCallback, useEffect, useRef, useState, forwardRef} from 'react'; +import {ScrollView, View} from 'react-native'; +import type {NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Browser from '@libs/Browser'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import getOperatingSystem from '@libs/getOperatingSystem'; +import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; +import CONST from '@src/CONST'; +import BigNumberPad from './BigNumberPad'; +import FormHelpMessage from './FormHelpMessage'; +import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; + +type AmountFormProps = { + /** Amount supplied by the FormProvider */ + value?: number; + + /** Currency supplied by user */ + currency?: string; + + /** Error to display at the bottom of the component */ + errorText?: string; + + /** Callback to update the amount in the FormProvider */ + onInputChange?: (value: number) => void; + + /** Fired when back button pressed, navigates to currency selection page */ + onCurrencyButtonPress: () => void; +}; + +/** + * Returns the new selection object based on the updated amount's length + */ +const getNewSelection = (oldSelection: {start: number, end: number}, prevLength: number, newLength: number) => { + const cursorPosition = oldSelection.end + (newLength - prevLength); + return {start: cursorPosition, end: cursorPosition}; +}; + +// const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; + +const AMOUNT_VIEW_ID = 'amountView'; +const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; +const NUM_PAD_VIEW_ID = 'numPadView'; + +function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText, onInputChange, onCurrencyButtonPress}: AmountFormProps, forwardedRef: ForwardedRef) { + const styles = useThemeStyles(); + const {toLocaleDigit, numberFormat} = useLocalize(); + + const textInput = useRef(null); + + const decimals = CurrencyUtils.getCurrencyDecimals(currency); + const selectedAmountAsString = amount.toString(); + + const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); + const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); + + const [selection, setSelection] = useState({ + start: selectedAmountAsString.length, + end: selectedAmountAsString.length, + }); + + const forwardDeletePressedRef = useRef(false); + + /** + * Event occurs when a user presses a mouse button over an DOM element. + */ + const onMouseDown = (event: React.MouseEvent, ids: string[]) => { + const relatedTargetId = (event.nativeEvent?.target as HTMLElement | null)?.id ?? ''; + if (!ids.includes(relatedTargetId)) { + return; + } + event.preventDefault(); + if (!textInput.current) { + return; + } + if (!textInput.current.isFocused()) { + textInput.current.focus(); + } + }; + + /** + * Sets the selection and the amount accordingly to the value passed to the input + * @param {String} newAmount - Changed amount from user input + */ + const setNewAmount = useCallback( + (newAmount: string) => { + // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value + // More info: https://github.com/Expensify/App/issues/16974 + const newAmountWithoutSpaces = MoneyRequestUtils.stripSpacesFromAmount(newAmount); + // Use a shallow copy of selection to trigger setSelection + // More info: https://github.com/Expensify/App/issues/16385 + if (!MoneyRequestUtils.validateAmount(newAmountWithoutSpaces, decimals)) { + setSelection((prevSelection) => ({...prevSelection})); + return; + } + + // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to + // setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet + // flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. + + let hasSelectionBeenSet = false; + setCurrentAmount((prevAmount) => { + const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); + const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current; + if (!hasSelectionBeenSet) { + hasSelectionBeenSet = true; + setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); + } + onInputChange?.(parseFloat(strippedAmount)); + return strippedAmount; + }); + }, + [decimals, onInputChange], + ); + + // Modifies the amount to match the decimals for changed currency. + useEffect(() => { + // If the changed currency supports decimals, we can return + if (MoneyRequestUtils.validateAmount(currentAmount, decimals)) { + return; + } + + // If the changed currency doesn't support decimals, we can strip the decimals + setNewAmount(MoneyRequestUtils.stripDecimalsFromAmount(currentAmount)); + + // we want to update only when decimals change (setNewAmount also changes when decimals change). + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [setNewAmount]); + + /** + * Update amount with number or Backspace pressed for BigNumberPad. + * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit to enable Next button + * + * @param {String} key + */ + const updateAmountNumberPad = useCallback( + (key: string) => { + if (shouldUpdateSelection && !textInput.current?.isFocused()) { + textInput.current?.focus(); + } + // Backspace button is pressed + if (key === '<' || key === 'Backspace') { + if (currentAmount.length > 0) { + const selectionStart = selection.start === selection.end ? selection.start - 1 : selection.start; + const newAmount = `${currentAmount.substring(0, selectionStart)}${currentAmount.substring(selection.end)}`; + setNewAmount(MoneyRequestUtils.addLeadingZero(newAmount)); + } + return; + } + const newAmount = MoneyRequestUtils.addLeadingZero(`${currentAmount.substring(0, selection.start)}${key}${currentAmount.substring(selection.end)}`); + setNewAmount(newAmount); + }, + [currentAmount, selection, shouldUpdateSelection, setNewAmount], + ); + + /** + * Update long press value, to remove items pressing on < + * + * @param {Boolean} value - Changed text from user input + */ + const updateLongPressHandlerState = useCallback((value: boolean) => { + setShouldUpdateSelection(!value); + if (!value && !textInput.current?.isFocused()) { + textInput.current?.focus(); + } + }, []); + + /** + * Input handler to check for a forward-delete key (or keyboard shortcut) press. + */ + const textInputKeyPress = (event: NativeSyntheticEvent) => { + const key = event.nativeEvent.key.toLowerCase(); + if (Browser.isMobileSafari() && key === CONST.PLATFORM_SPECIFIC_KEYS.CTRL.DEFAULT) { + // Optimistically anticipate forward-delete on iOS Safari (in cases where the Mac Accessiblity keyboard is being + // used for input). If the Control-D shortcut doesn't get sent, the ref will still be reset on the next key press. + forwardDeletePressedRef.current = true; + return; + } + // Control-D on Mac is a keyboard shortcut for forward-delete. See https://support.apple.com/en-us/HT201236 for Mac keyboard shortcuts. + // Also check for the keyboard shortcut on iOS in cases where a hardware keyboard may be connected to the device. + const operatingSystem = getOperatingSystem() as string | null; + const allowedOS: string[] = [CONST.OS.MAC_OS, CONST.OS.IOS]; + forwardDeletePressedRef.current = key === 'delete' || (allowedOS.includes(operatingSystem ?? '') && event.nativeEvent.ctrlKey && key === 'd'); + }; + + const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); + const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); + + return ( + + onMouseDown(event, [AMOUNT_VIEW_ID])} + style={[styles.moneyRequestAmountContainer, styles.flex1, styles.flexRow, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]} + > + { + if (typeof forwardedRef === 'function') { + forwardedRef(ref); + } else if (forwardedRef && 'current' in forwardedRef) { + // eslint-disable-next-line no-param-reassign + forwardedRef.current = ref; + } + textInput.current = ref; + }} + selectedCurrencyCode={currency} + selection={selection} + onSelectionChange={(e: NativeSyntheticEvent) => { + if (!shouldUpdateSelection) { + return; + } + setSelection(e.nativeEvent.selection); + }} + onKeyPress={textInputKeyPress} + /> + {!!errorText && ( + + )} + + onMouseDown(event, [NUM_PAD_CONTAINER_VIEW_ID, NUM_PAD_VIEW_ID])} + style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper, styles.pt0]} + id={NUM_PAD_CONTAINER_VIEW_ID} + > + {canUseTouchScreen ? ( + + ) : null} + + + ); +} + +AmountForm.displayName = 'AmountForm'; + +export default forwardRef(AmountForm); diff --git a/src/components/BigNumberPad.tsx b/src/components/BigNumberPad.tsx index 8b840f9d1b57..adefb78a66c7 100644 --- a/src/components/BigNumberPad.tsx +++ b/src/components/BigNumberPad.tsx @@ -17,7 +17,7 @@ type BigNumberPadProps = { id?: string; /** Whether long press is disabled */ - isLongPressDisabled: boolean; + isLongPressDisabled?: boolean; }; const padNumbers = [ diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index e2aff7243d1a..48265259f82a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -229,7 +229,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceSettingsCurrencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.CARD]: () => require('../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType, [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType, - [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default as React.ComponentType, [SCREENS.WORKSPACE.BILLS]: () => require('../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType, [SCREENS.WORKSPACE.INVOICES]: () => require('../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType, [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index d13f81635277..336a7bd226d8 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -246,6 +246,12 @@ const linkingConfig: LinkingOptions = { [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: { path: ROUTES.WORKSPACE_RATE_AND_UNIT.route, }, + [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: { + path: ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.route, + }, + [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: { + path: ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.route, + }, [SCREENS.WORKSPACE.BILLS]: { path: ROUTES.WORKSPACE_BILLS.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 2bd47f907e02..b1394327d7e4 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -96,6 +96,8 @@ type SettingsNavigatorParamList = { policyID: string; }; [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: undefined; + [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: undefined; + [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: undefined; [SCREENS.WORKSPACE.BILLS]: { policyID: string; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index cbbc00dd42fc..a2dabf757a8e 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1459,6 +1459,9 @@ function openWorkspaceReimburseView(policyID: string) { return; } + // @ts-expect-error TODO: Fix this later + Onyx.set(ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM, {policyID}); + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js similarity index 95% rename from src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js rename to src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index 614255af4497..b669ffb173bb 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -1,7 +1,5 @@ import lodashGet from 'lodash/get'; import React, {useEffect} from 'react'; -// import {Keyboard, View} from 'react-native'; -import {Keyboard} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FormProvider from '@components/Form/FormProvider'; @@ -35,12 +33,13 @@ const propTypes = { const defaultProps = { reimbursementAccount: {}, + formDraft: {}, ...policyDefaultProps, }; function WorkspaceRateAndUnitPage(props) { useEffect(() => { - if (lodashGet(props, 'policy.customUnits', []).length !== 0) { + if (lodashGet(props, 'policy.customUnits', []).length !== 0 && lodashGet(props, 'formDraft.policyID', '') === props.policy.id) { return; } @@ -77,7 +76,6 @@ function WorkspaceRateAndUnitPage(props) { const submit = (values) => { saveUnitAndRate(values.unit, values.rate); - Keyboard.dismiss(); Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy.id)); }; @@ -158,6 +156,7 @@ function WorkspaceRateAndUnitPage(props) { title={props.translate('workspace.reimburse.trackDistanceRate')} customValueRenderer={(value) => CurrencyUtils.convertAmountToDisplayString(value, lodashGet(props, 'policy.outputCurrency', CONST.CURRENCY.USD))} shouldShowRightIcon + shouldSaveDraft /> unitItems[value]} shouldShowRightIcon + shouldSaveDraft + onPress={() => { + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id)); + }} /> @@ -186,6 +189,9 @@ export default compose( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, + formDraft: { + key: ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT, + }, }), withThemeStyles, )(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx new file mode 100644 index 000000000000..4d77f15ac534 --- /dev/null +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -0,0 +1,98 @@ +import React, { useMemo } from 'react'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import SelectionList from '@components/SelectionList'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import compose from '@libs/compose'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import type * as OnyxTypes from '@src/types/onyx'; + +type WorkspaceUnitPageOnyxProps = { + /** Draft form data related to workspaceRateAndUnitForm */ + formDraft: OnyxEntry; +}; + +type OptionRow = { + value: string; + text: string; + keyForList: string; + isSelected: boolean; +}; + +type WorkspaceUnitPageProps = WithPolicyProps & WorkspaceUnitPageOnyxProps; + +function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const unitItems = useMemo(() => ({ + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: translate('common.kilometers'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: translate('common.miles'), + }),[translate]); + + const updateUnit = (unit: string) => { + // @ts-expect-error This is a problem with Form Draft type + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT, {unit}); + Navigation.goBack(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); + } + + const defaultValue = useMemo(() => { + const defaultDistanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + return defaultDistanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; + }, [props.policy?.customUnits]); + + const unitOptions = useMemo(() => { + const arr: OptionRow[] = []; + Object.entries(unitItems).forEach(([unit, label]) => { + arr.push({ + value: unit, + text: label, + keyForList: unit, + // @ts-expect-error This is a problem with Form Draft type + isSelected: (props.formDraft?.unit || defaultValue) === unit, + }); + }); + return arr; + // @ts-expect-error This is a problem with Form Draft type + }, [defaultValue, props.formDraft?.unit, unitItems]); + + return ( + + + {translate('themePage.chooseThemeBelowOrSync')} + + updateUnit(unit.value)} + initiallyFocusedOptionKey={unitOptions.find((unit) => unit.isSelected)?.keyForList} + /> + + ); +} + +WorkspaceUnitPage.displayName = 'WorkspaceUnitPage'; + +export default compose( + withOnyx({ + formDraft: { + key: ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT, + }, + }), + withPolicy, +)(WorkspaceUnitPage); From 126eedc865ef52e45ca7be3be361d17d8f7102f5 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 21 Jan 2024 21:07:03 +0530 Subject: [PATCH 04/35] WIP: bug fixes --- src/ROUTES.ts | 4 +- .../WorkspaceRateAndUnitPage/BasePage.js | 19 ++++--- .../WorkspaceRateAndUnitPage/UnitPage.tsx | 57 +++++++------------ 3 files changed, 33 insertions(+), 47 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 613d781eeb44..312f67ac4d6d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -448,7 +448,7 @@ const ROUTES = { }, WORKSPACE_RATE_AND_UNIT: { route: 'workspace/:policyID/rateandunit', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit` as const, + getRoute: (policyID: string, unit?: string) => `workspace/${policyID}/rateandunit${unit ? `?unit=${unit}` : ''}` as const, }, WORKSPACE_RATE_AND_UNIT_RATE: { route: 'workspace/:policyID/rateandunit/rate', @@ -456,7 +456,7 @@ const ROUTES = { }, WORKSPACE_RATE_AND_UNIT_UNIT: { route: 'workspace/:policyID/rateandunit/unit', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/unit` as const, + getRoute: (policyID: string, unit?: string) => `workspace/${policyID}/rateandunit/unit${unit ? `?unit=${unit}` : ''}` as const, }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index b669ffb173bb..8396e846aa8f 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; import React, {useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; @@ -26,6 +27,12 @@ import ROUTES from '@src/ROUTES'; import FormMenuItem from '@components/FormMenuItem'; const propTypes = { + route: PropTypes.shape({ + params: PropTypes.shape({ + policyID: PropTypes.string.isRequired, + unit: PropTypes.string, + }).isRequired, + }).isRequired, ...policyPropTypes, ...withLocalizePropTypes, ...withThemeStylesPropTypes, @@ -33,13 +40,12 @@ const propTypes = { const defaultProps = { reimbursementAccount: {}, - formDraft: {}, ...policyDefaultProps, }; function WorkspaceRateAndUnitPage(props) { useEffect(() => { - if (lodashGet(props, 'policy.customUnits', []).length !== 0 && lodashGet(props, 'formDraft.policyID', '') === props.policy.id) { + if (lodashGet(props, 'policy.customUnits', []).length !== 0) { return; } @@ -156,18 +162,16 @@ function WorkspaceRateAndUnitPage(props) { title={props.translate('workspace.reimburse.trackDistanceRate')} customValueRenderer={(value) => CurrencyUtils.convertAmountToDisplayString(value, lodashGet(props, 'policy.outputCurrency', CONST.CURRENCY.USD))} shouldShowRightIcon - shouldSaveDraft /> unitItems[value]} shouldShowRightIcon - shouldSaveDraft onPress={() => { - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id)); + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id, props.route.params.unit || lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES))); }} /> @@ -189,9 +193,6 @@ export default compose( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, - formDraft: { - key: ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT, - }, }), withThemeStyles, )(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index 4d77f15ac534..c076581ded81 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -1,24 +1,15 @@ import React, { useMemo } from 'react'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import compose from '@libs/compose'; import withPolicy from '@pages/workspace/withPolicy'; -import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import type * as OnyxTypes from '@src/types/onyx'; - -type WorkspaceUnitPageOnyxProps = { - /** Draft form data related to workspaceRateAndUnitForm */ - formDraft: OnyxEntry; -}; +import type {RouteProp} from '@react-navigation/native'; type OptionRow = { value: string; @@ -27,7 +18,9 @@ type OptionRow = { isSelected: boolean; }; -type WorkspaceUnitPageProps = WithPolicyProps & WorkspaceUnitPageOnyxProps; +type WorkspaceUnitPageProps = WithPolicyOnyxProps & { + route: RouteProp<{params: {policyID: string; unit?: string}}>; +}; function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { const styles = useThemeStyles(); @@ -38,10 +31,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { }),[translate]); const updateUnit = (unit: string) => { - // @ts-expect-error This is a problem with Form Draft type - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT, {unit}); - Navigation.goBack(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', unit)); } const defaultValue = useMemo(() => { @@ -56,13 +46,12 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { value: unit, text: label, keyForList: unit, - // @ts-expect-error This is a problem with Form Draft type - isSelected: (props.formDraft?.unit || defaultValue) === unit, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + isSelected: (props.route.params.unit || defaultValue) === unit, }); }); return arr; - // @ts-expect-error This is a problem with Form Draft type - }, [defaultValue, props.formDraft?.unit, unitItems]); + }, [defaultValue, props.route.params.unit, unitItems]); return ( + {() => ( + <> + {translate('themePage.chooseThemeBelowOrSync')} - {translate('themePage.chooseThemeBelowOrSync')} - - updateUnit(unit.value)} - initiallyFocusedOptionKey={unitOptions.find((unit) => unit.isSelected)?.keyForList} - /> + updateUnit(unit.value)} + initiallyFocusedOptionKey={unitOptions.find((unit) => unit.isSelected)?.keyForList} + /> + + )} ); } WorkspaceUnitPage.displayName = 'WorkspaceUnitPage'; -export default compose( - withOnyx({ - formDraft: { - key: ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT, - }, - }), - withPolicy, -)(WorkspaceUnitPage); +export default withPolicy(WorkspaceUnitPage); From eafe71d52c3d2052982dbd9275477c15fdd7ce06 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 21 Jan 2024 21:17:15 +0530 Subject: [PATCH 05/35] WIP: bug fixes 2 --- src/libs/PolicyUtils.ts | 1 + src/libs/actions/Policy.ts | 3 --- .../reimburse/WorkspaceRateAndUnitPage/BasePage.js | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 0cab97299324..402debe6ed2f 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -210,6 +210,7 @@ export { hasCustomUnitsError, getNumericValue, getUnitRateValue, + getRateDisplayValue, getPolicyBrickRoadIndicatorStatus, shouldShowPolicy, isExpensifyTeam, diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index a2dabf757a8e..cbbc00dd42fc 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1459,9 +1459,6 @@ function openWorkspaceReimburseView(policyID: string) { return; } - // @ts-expect-error TODO: Fix this later - Onyx.set(ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM, {policyID}); - const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index 8396e846aa8f..d598e7da4dc9 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -66,7 +66,6 @@ function WorkspaceRateAndUnitPage(props) { 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, @@ -74,7 +73,7 @@ function WorkspaceRateAndUnitPage(props) { attributes: {unit}, rates: { ...currentCustomUnitRate, - rate: rateNumValue * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, + rate, }, }; Policy.updateWorkspaceCustomUnitAndRate(props.policy.id, distanceCustomUnit, newCustomUnit, props.policy.lastModified); @@ -87,13 +86,14 @@ function WorkspaceRateAndUnitPage(props) { const validate = (values) => { const errors = {}; + const parsedRate = PolicyUtils.getRateDisplayValue(values.rate / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, props.toLocaleDigit); 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 === '') { + if (!rateValueRegex.test(parsedRate) || parsedRate === '') { errors.rate = 'workspace.reimburse.invalidRateError'; - } else if (NumberUtils.parseFloatAnyLocale(values.rate) <= 0) { + } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { errors.rate = 'workspace.reimburse.lowRateError'; } return errors; From ef54837180643251f29cbd671e2afbb9c2ba1ceb Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 21 Jan 2024 22:21:43 +0530 Subject: [PATCH 06/35] WIP: rate page implementation --- src/ROUTES.ts | 9 +- .../WorkspaceRateAndUnitPage/BasePage.js | 13 ++- .../WorkspaceRateAndUnitPage/RatePage.tsx | 88 +++++++++++++++++++ .../WorkspaceRateAndUnitPage/UnitPage.tsx | 4 +- 4 files changed, 106 insertions(+), 8 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 312f67ac4d6d..01bbfc8ba9b3 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -448,15 +448,18 @@ const ROUTES = { }, WORKSPACE_RATE_AND_UNIT: { route: 'workspace/:policyID/rateandunit', - getRoute: (policyID: string, unit?: string) => `workspace/${policyID}/rateandunit${unit ? `?unit=${unit}` : ''}` as const, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + getRoute: (policyID: string, unit?: string, rate?: string) => `workspace/${policyID}/rateandunit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, }, WORKSPACE_RATE_AND_UNIT_RATE: { route: 'workspace/:policyID/rateandunit/rate', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/rate` as const, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + getRoute: (policyID: string, unit?: string, rate?: string) => `workspace/${policyID}/rateandunit/rate${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, }, WORKSPACE_RATE_AND_UNIT_UNIT: { route: 'workspace/:policyID/rateandunit/unit', - getRoute: (policyID: string, unit?: string) => `workspace/${policyID}/rateandunit/unit${unit ? `?unit=${unit}` : ''}` as const, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + getRoute: (policyID: string, unit?: string, rate?: string) => `workspace/${policyID}/rateandunit/unit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index d598e7da4dc9..8ba5e66741d2 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -31,6 +31,7 @@ const propTypes = { params: PropTypes.shape({ policyID: PropTypes.string.isRequired, unit: PropTypes.string, + rate: PropTypes.string, }).isRequired, }).isRequired, ...policyPropTypes, @@ -102,6 +103,9 @@ function WorkspaceRateAndUnitPage(props) { 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 unitValue = props.route.params.unit || lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + const rateValue = props.route.params.rate || distanceCustomRate.rate.toString(); + return ( CurrencyUtils.convertAmountToDisplayString(value, lodashGet(props, 'policy.outputCurrency', CONST.CURRENCY.USD))} shouldShowRightIcon + onPress={() => { + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.getRoute(props.policy.id, unitValue, rateValue)); + }} /> unitItems[value]} shouldShowRightIcon onPress={() => { - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id, props.route.params.unit || lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES))); + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id, unitValue, rateValue)); }} /> diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index e69de29bb2d1..0b4f76e1020d 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -0,0 +1,88 @@ +import React, { useMemo } from 'react'; +import useLocalize from '@hooks/useLocalize'; +// import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import type {RouteProp} from '@react-navigation/native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapperWithRef from '@components/Form/InputWrapper'; +import AmountForm from '@components/AmountForm'; +import ONYXKEYS from '@src/ONYXKEYS'; +import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as NumberUtils from '@libs/NumberUtils'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; + +type WorkspaceUnitPageProps = WithPolicyOnyxProps & { + route: RouteProp<{params: {policyID: string; unit?: string; rate?: string;}}>; +}; + +function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { + // const styles = useThemeStyles(); + const {translate, toLocaleDigit} = useLocalize(); + + const submit = (values: {rateEdit: number;}) => { + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', props.route.params.unit, (values.rateEdit * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString())); + } + + const validate = (values: {rateEdit: number;}) => { + const errors: {rateEdit?: string;} = {}; + const parsedRate = PolicyUtils.getRateDisplayValue(values.rateEdit, toLocaleDigit); + const decimalSeparator = toLocaleDigit('.'); + const outputCurrency = 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(parsedRate) || parsedRate === '') { + errors.rateEdit = 'workspace.reimburse.invalidRateError'; + } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { + errors.rateEdit = 'workspace.reimburse.lowRateError'; + } + return errors; + }; + + const defaultValue = useMemo(() => { + const defaultDistanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomRate = Object.values(defaultDistanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + return distanceCustomRate?.rate ?? 0; + }, [props.policy?.customUnits]); + + return ( + + {() => ( + // @ts-expect-error Migration Pending + + {}} + defaultValue={props.route.params.rate} + currency={props.policy?.outputCurrency ?? CONST.CURRENCY.USD} + value={(typeof props.route.params.rate === 'string' ? parseFloat(props.route.params.rate) : defaultValue) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET} + /> + + )} + + ); +} + +WorkspaceUnitPage.displayName = 'WorkspaceUnitPage'; + +export default withPolicy(WorkspaceUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index c076581ded81..bab045304750 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -19,7 +19,7 @@ type OptionRow = { }; type WorkspaceUnitPageProps = WithPolicyOnyxProps & { - route: RouteProp<{params: {policyID: string; unit?: string}}>; + route: RouteProp<{params: {policyID: string; unit?: string; rate?: string;}}>; }; function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { @@ -31,7 +31,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { }),[translate]); const updateUnit = (unit: string) => { - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', unit)); + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', unit, props.route.params.rate)); } const defaultValue = useMemo(() => { From bfa1572240ff83ab7bcb435f5c77d64fc2c35fa8 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 21 Jan 2024 23:30:38 +0530 Subject: [PATCH 07/35] Visual fixes --- src/components/AmountForm.tsx | 24 +++++++++---------- src/components/Form/FormWrapper.js | 8 ++++++- .../WorkspaceRateAndUnitPage/RatePage.tsx | 11 +++++---- .../WorkspaceRateAndUnitPage/UnitPage.tsx | 2 +- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 848b738815f2..d6a956e8888d 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -1,6 +1,6 @@ import type {ForwardedRef} from 'react'; import React, {useCallback, useEffect, useRef, useState, forwardRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -190,7 +190,7 @@ function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); return ( - + <> onMouseDown(event, [AMOUNT_VIEW_ID])} @@ -223,26 +223,26 @@ function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText /> {!!errorText && ( )} - onMouseDown(event, [NUM_PAD_CONTAINER_VIEW_ID, NUM_PAD_VIEW_ID])} - style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper, styles.pt0]} - id={NUM_PAD_CONTAINER_VIEW_ID} - > - {canUseTouchScreen ? ( + {canUseTouchScreen ? ( + onMouseDown(event, [NUM_PAD_CONTAINER_VIEW_ID, NUM_PAD_VIEW_ID])} + style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper, styles.pt0]} + id={NUM_PAD_CONTAINER_VIEW_ID} + > - ) : null} - - + + ) : null} + ); } diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index f1c5d6de9071..9c381bc27e68 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -60,6 +60,9 @@ const propTypes = { /** Submit button styles */ submitButtonStyles: stylePropTypes, + /** Whether to apply flex to the submit button */ + submitFlexEnabled: PropTypes.bool, + /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), @@ -81,6 +84,7 @@ const defaultProps = { footerContent: null, style: [], submitButtonStyles: [], + submitFlexEnabled: true, shouldHideFixErrorsAlert: false, }; @@ -97,6 +101,7 @@ function FormWrapper(props) { isSubmitButtonVisible, style, submitButtonStyles, + submitFlexEnabled, enabledWhenOffline, isSubmitActionDangerous, formID, @@ -153,7 +158,7 @@ function FormWrapper(props) { focusInput.focus(); } }} - containerStyles={[styles.mh0, styles.mt5, styles.flex1, ...submitButtonStyles]} + containerStyles={[styles.mh0, styles.mt5, submitFlexEnabled ? styles.flex1 : {}, ...submitButtonStyles]} enabledWhenOffline={enabledWhenOffline} isSubmitActionDangerous={isSubmitActionDangerous} disablePressOnEnter @@ -180,6 +185,7 @@ function FormWrapper(props) { styles.mh0, styles.mt5, submitButtonStyles, + submitFlexEnabled, submitButtonText, shouldHideFixErrorsAlert, ], diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 0b4f76e1020d..68b64a78cace 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import useLocalize from '@hooks/useLocalize'; -// import useThemeStyles from '@hooks/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -22,7 +22,7 @@ type WorkspaceUnitPageProps = WithPolicyOnyxProps & { }; function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { - // const styles = useThemeStyles(); + const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const submit = (values: {rateEdit: number;}) => { @@ -56,7 +56,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} shouldSkipVBBACall - backButtonRoute={ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy?.id ?? '')} + backButtonRoute={ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')} shouldShowLoading={false} > {() => ( @@ -67,15 +67,16 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { validate={validate} onSubmit={submit} enabledWhenOffline + style={[styles.flexGrow1, styles.mh5]} + submitFlexEnabled={false} > {}} - defaultValue={props.route.params.rate} currency={props.policy?.outputCurrency ?? CONST.CURRENCY.USD} - value={(typeof props.route.params.rate === 'string' ? parseFloat(props.route.params.rate) : defaultValue) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET} + defaultValue={(typeof props.route.params.rate === 'string' ? parseFloat(props.route.params.rate) : defaultValue) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET} /> )} diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index bab045304750..fd2b8be92073 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -59,7 +59,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} shouldSkipVBBACall - backButtonRoute={ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy?.id ?? '')} + backButtonRoute={ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')} shouldShowLoading={false} > {() => ( From e0060f655068634209092d61f07914900eacb10f Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 21 Jan 2024 23:34:47 +0530 Subject: [PATCH 08/35] prettier fix --- src/ROUTES.ts | 15 ++++---- src/components/AmountForm.tsx | 8 ++--- src/components/FormMenuItem.tsx | 2 +- .../WorkspaceRateAndUnitPage/BasePage.js | 4 +-- .../WorkspaceRateAndUnitPage/RatePage.tsx | 36 ++++++++++--------- .../WorkspaceRateAndUnitPage/UnitPage.tsx | 23 ++++++------ 6 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 01bbfc8ba9b3..6b824789cbf6 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -448,18 +448,21 @@ const ROUTES = { }, WORKSPACE_RATE_AND_UNIT: { route: 'workspace/:policyID/rateandunit', - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - getRoute: (policyID: string, unit?: string, rate?: string) => `workspace/${policyID}/rateandunit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, + getRoute: (policyID: string, unit?: string, rate?: string) => + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + `workspace/${policyID}/rateandunit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, }, WORKSPACE_RATE_AND_UNIT_RATE: { route: 'workspace/:policyID/rateandunit/rate', - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - getRoute: (policyID: string, unit?: string, rate?: string) => `workspace/${policyID}/rateandunit/rate${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, + getRoute: (policyID: string, unit?: string, rate?: string) => + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + `workspace/${policyID}/rateandunit/rate${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, }, WORKSPACE_RATE_AND_UNIT_UNIT: { route: 'workspace/:policyID/rateandunit/unit', - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - getRoute: (policyID: string, unit?: string, rate?: string) => `workspace/${policyID}/rateandunit/unit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, + getRoute: (policyID: string, unit?: string, rate?: string) => + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + `workspace/${policyID}/rateandunit/unit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index d6a956e8888d..eb9b968b05eb 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {useCallback, useEffect, useRef, useState, forwardRef} from 'react'; +import React, {forwardRef, useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -25,7 +25,7 @@ type AmountFormProps = { errorText?: string; /** Callback to update the amount in the FormProvider */ - onInputChange?: (value: number) => void; + onInputChange?: (value: number) => void; /** Fired when back button pressed, navigates to currency selection page */ onCurrencyButtonPress: () => void; @@ -34,7 +34,7 @@ type AmountFormProps = { /** * Returns the new selection object based on the updated amount's length */ -const getNewSelection = (oldSelection: {start: number, end: number}, prevLength: number, newLength: number) => { +const getNewSelection = (oldSelection: {start: number; end: number}, prevLength: number, newLength: number) => { const cursorPosition = oldSelection.end + (newLength - prevLength); return {start: cursorPosition, end: cursorPosition}; }; @@ -97,7 +97,7 @@ function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText return; } - // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to + // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to // setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet // flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. diff --git a/src/components/FormMenuItem.tsx b/src/components/FormMenuItem.tsx index 8455763230ff..8ce81bb440c7 100644 --- a/src/components/FormMenuItem.tsx +++ b/src/components/FormMenuItem.tsx @@ -10,7 +10,7 @@ type FormMenuItemProps = (NoIcon | AvatarProps | IconProps) & value?: string; /** Custom value renderer to render description based on form values */ - customValueRenderer?: (value?: string) => (string | undefined); + customValueRenderer?: (value?: string) => string | undefined; }; function FormMenuItem({customValueRenderer, value, errorText, ...props}: FormMenuItemProps, ref: ForwardedRef) { diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index 8ba5e66741d2..e17eaaa9426e 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -1,10 +1,11 @@ -import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import FormMenuItem from '@components/FormMenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {withNetwork} from '@components/OnyxProvider'; // import Picker from '@components/Picker'; @@ -24,7 +25,6 @@ import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import FormMenuItem from '@components/FormMenuItem'; const propTypes = { route: PropTypes.shape({ diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 68b64a78cace..790bce06499a 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -1,36 +1,38 @@ -import React, { useMemo } from 'react'; +import type {RouteProp} from '@react-navigation/native'; +import React, {useMemo} from 'react'; +import AmountForm from '@components/AmountForm'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapperWithRef from '@components/Form/InputWrapper'; 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 CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; +import * as NumberUtils from '@libs/NumberUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import type {RouteProp} from '@react-navigation/native'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapperWithRef from '@components/Form/InputWrapper'; -import AmountForm from '@components/AmountForm'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as NumberUtils from '@libs/NumberUtils'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import ROUTES from '@src/ROUTES'; type WorkspaceUnitPageProps = WithPolicyOnyxProps & { - route: RouteProp<{params: {policyID: string; unit?: string; rate?: string;}}>; + route: RouteProp<{params: {policyID: string; unit?: string; rate?: string}}>; }; function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const submit = (values: {rateEdit: number;}) => { - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', props.route.params.unit, (values.rateEdit * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString())); - } + const submit = (values: {rateEdit: number}) => { + Navigation.navigate( + ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', props.route.params.unit, (values.rateEdit * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()), + ); + }; - const validate = (values: {rateEdit: number;}) => { - const errors: {rateEdit?: string;} = {}; + const validate = (values: {rateEdit: number}) => { + const errors: {rateEdit?: string} = {}; const parsedRate = PolicyUtils.getRateDisplayValue(values.rateEdit, toLocaleDigit); const decimalSeparator = toLocaleDigit('.'); const outputCurrency = props.policy?.outputCurrency ?? CONST.CURRENCY.USD; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index fd2b8be92073..8b3351d4c960 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -1,15 +1,15 @@ -import React, { useMemo } from 'react'; +import type {RouteProp} from '@react-navigation/native'; +import React, {useMemo} from 'react'; import SelectionList from '@components/SelectionList'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import type {RouteProp} from '@react-navigation/native'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; type OptionRow = { value: string; @@ -19,20 +19,23 @@ type OptionRow = { }; type WorkspaceUnitPageProps = WithPolicyOnyxProps & { - route: RouteProp<{params: {policyID: string; unit?: string; rate?: string;}}>; + route: RouteProp<{params: {policyID: string; unit?: string; rate?: string}}>; }; function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const unitItems = useMemo(() => ({ - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: translate('common.kilometers'), - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: translate('common.miles'), - }),[translate]); + const unitItems = useMemo( + () => ({ + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: translate('common.kilometers'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: translate('common.miles'), + }), + [translate], + ); const updateUnit = (unit: string) => { Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', unit, props.route.params.rate)); - } + }; const defaultValue = useMemo(() => { const defaultDistanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); From 0643d90ebb8fda1d8dc6d5c2d11f267d57a1dce4 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 31 Jan 2024 18:29:39 +0530 Subject: [PATCH 09/35] merge main --- src/components/Form/FormWrapper.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index d5b47761e4c0..7f95c6e71943 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -28,6 +28,9 @@ type FormWrapperProps = ChildrenProps & /** Submit button styles */ submitButtonStyles?: StyleProp; + /** Whether to apply flex to the submit button */ + submitFlexEnabled?: boolean; + /** Server side errors keyed by microtime */ errors: Errors; @@ -49,6 +52,7 @@ function FormWrapper({ isSubmitButtonVisible = true, style, submitButtonStyles, + submitFlexEnabled = true, enabledWhenOffline, isSubmitActionDangerous = false, formID, @@ -109,7 +113,7 @@ function FormWrapper({ onSubmit={onSubmit} footerContent={footerContent} onFixTheErrorsLinkPressed={onFixTheErrorsLinkPressed} - containerStyles={[styles.mh0, styles.mt5, styles.flex1, submitButtonStyles]} + containerStyles={[styles.mh0, styles.mt5, submitFlexEnabled ? styles.flex1 : {}, submitButtonStyles]} enabledWhenOffline={enabledWhenOffline} isSubmitActionDangerous={isSubmitActionDangerous} disablePressOnEnter @@ -134,6 +138,7 @@ function FormWrapper({ styles.mh0, styles.mt5, submitButtonStyles, + submitFlexEnabled, submitButtonText, shouldHideFixErrorsAlert, onFixTheErrorsLinkPressed, From 6cee6d506e8ac3133e7b7793b084009c0759d491 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Wed, 31 Jan 2024 19:12:23 +0530 Subject: [PATCH 10/35] ts fix --- src/components/AmountForm.tsx | 6 ++---- src/components/Form/FormProvider.tsx | 4 +++- src/components/Form/types.ts | 7 ++++--- .../WorkspaceRateAndUnitPage/RatePage.tsx | 17 ++++++++--------- .../WorkspaceRateAndUnitPage/UnitPage.tsx | 1 - src/types/onyx/Form.ts | 2 +- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index eb9b968b05eb..df3256679074 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -28,7 +28,7 @@ type AmountFormProps = { onInputChange?: (value: number) => void; /** Fired when back button pressed, navigates to currency selection page */ - onCurrencyButtonPress: () => void; + onCurrencyButtonPress?: () => void; }; /** @@ -39,8 +39,6 @@ const getNewSelection = (oldSelection: {start: number; end: number}, prevLength: return {start: cursorPosition, end: cursorPosition}; }; -// const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; - const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; @@ -197,7 +195,7 @@ function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText style={[styles.moneyRequestAmountContainer, styles.flex1, styles.flexRow, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]} > void; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 790bce06499a..f256831b914e 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -3,6 +3,7 @@ import React, {useMemo} from 'react'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; +import type {OnyxFormValuesFields} from '@components/Form/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; @@ -25,15 +26,15 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const submit = (values: {rateEdit: number}) => { - Navigation.navigate( - ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', props.route.params.unit, (values.rateEdit * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()), - ); + const submit = (values: OnyxFormValuesFields) => { + const rateEdit = values.rateEdit as number; + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', props.route.params.unit, (rateEdit * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString())); }; - const validate = (values: {rateEdit: number}) => { + const validate = (values: OnyxFormValuesFields) => { const errors: {rateEdit?: string} = {}; - const parsedRate = PolicyUtils.getRateDisplayValue(values.rateEdit, toLocaleDigit); + const rateEdit = values.rateEdit as number; + const parsedRate = PolicyUtils.getRateDisplayValue(rateEdit, toLocaleDigit); const decimalSeparator = toLocaleDigit('.'); const outputCurrency = props.policy?.outputCurrency ?? CONST.CURRENCY.USD; // Allow one more decimal place for accuracy @@ -62,7 +63,6 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { shouldShowLoading={false} > {() => ( - // @ts-expect-error Migration Pending {}} currency={props.policy?.outputCurrency ?? CONST.CURRENCY.USD} defaultValue={(typeof props.route.params.rate === 'string' ? parseFloat(props.route.params.rate) : defaultValue) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET} /> diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index 8b3351d4c960..095a740d03e3 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -70,7 +70,6 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { {translate('themePage.chooseThemeBelowOrSync')} updateUnit(unit.value)} initiallyFocusedOptionKey={unitOptions.find((unit) => unit.isSelected)?.keyForList} diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index c015627bfbc2..34cf722f90fb 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -1,7 +1,7 @@ import type * as OnyxCommon from './OnyxCommon'; import type PersonalBankAccount from './PersonalBankAccount'; -type FormValueType = string | boolean | Date | OnyxCommon.Errors; +type FormValueType = string | boolean | number | Date | OnyxCommon.Errors; type BaseForm = { /** Controls the loading state of the form */ From cff1aeec50c2ab6d20d0c263121bf0b701bff6d1 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 1 Feb 2024 17:01:30 +0530 Subject: [PATCH 11/35] added better translations --- src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ .../workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 3 ++- .../workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx | 4 ++-- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 58af1c6638c4..daa63f5ddd78 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1589,7 +1589,10 @@ export default { trackDistance: 'Track distance', trackDistanceCopy: 'Set the per mile/km rate and choose a default unit to track.', trackDistanceRate: 'Rate', + trackDistanceRateTitle: 'Track distance: Rate', trackDistanceUnit: 'Unit', + trackDistanceUnitTitle: 'Track distance: Unit', + trackDistanceChooseUnit: 'Choose a default unit to track.', unlockNextDayReimbursements: 'Unlock next-day reimbursements', captureNoVBACopyBeforeEmail: 'Ask your workspace members to forward receipts to ', captureNoVBACopyAfterEmail: ' and download the Expensify App to track cash expenses on the go.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 427097d5d16b..02596732293e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1612,7 +1612,10 @@ export default { trackDistance: 'Medir distancia', trackDistanceCopy: 'Configura la tarifa y unidad usadas para medir distancias.', trackDistanceRate: 'Tarifa', + trackDistanceRateTitle: 'Medir distancia: Tarifa', trackDistanceUnit: 'Unidad', + trackDistanceUnitTitle: 'Medir distancia: Unidad', + trackDistanceChooseUnit: 'Elija una unidad predeterminada para rastrear.', unlockNextDayReimbursements: 'Desbloquea reembolsos diarios', captureNoVBACopyBeforeEmail: 'Pide a los miembros de tu espacio de trabajo que envíen recibos a ', captureNoVBACopyAfterEmail: ' y descarga la App de Expensify para controlar tus gastos en efectivo sobre la marcha.', diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index f256831b914e..75721d4ae55b 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -55,7 +55,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { return ( diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index 095a740d03e3..95d3644f486a 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -58,7 +58,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { return ( {() => ( <> - {translate('themePage.chooseThemeBelowOrSync')} + {translate('workspace.reimburse.trackDistanceChooseUnit')} Date: Thu, 1 Feb 2024 17:04:18 +0530 Subject: [PATCH 12/35] corrected types --- src/libs/Navigation/types.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index e1940951347e..96aea43be690 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -95,9 +95,15 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.REIMBURSE]: { policyID: string; }; - [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: undefined; - [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: undefined; - [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: undefined; + [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: { + policyID: string; + }; [SCREENS.WORKSPACE.BILLS]: { policyID: string; }; From c4cdb9b8d23283f4a3ea934a2117529856eec3c0 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 2 Feb 2024 18:15:52 +0530 Subject: [PATCH 13/35] using onyx instead of route query param to pass the values --- src/ONYXKEYS.ts | 4 + src/ROUTES.ts | 12 +- src/components/FormMenuItem.tsx | 32 --- .../WorkspaceRateAndUnitPage/BasePage.js | 183 ++++++++---------- .../WorkspaceRateAndUnitPage/RatePage.tsx | 63 ++++-- .../WorkspaceRateAndUnitPage/UnitPage.tsx | 45 ++++- .../reimburse/WorkspaceReimburseView.js | 9 +- src/types/onyx/WorkspaceRateAndUnit.ts | 12 ++ src/types/onyx/index.ts | 2 + 9 files changed, 186 insertions(+), 176 deletions(-) delete mode 100644 src/components/FormMenuItem.tsx create mode 100644 src/types/onyx/WorkspaceRateAndUnit.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index c90a7a6a7c74..2e5e46d59fd4 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -63,6 +63,9 @@ const ONYXKEYS = { /** Contains all the info for Tasks */ TASK: 'task', + /** Contains all the info for Workspace Rate and Unit (editing) */ + WORKSPACE_RATE_AND_UNIT: 'workspaceRateAndUnit', + /** Contains a list of all currencies available to the user - user can * select a currency based on the list */ CURRENCY_LIST: 'currencyList', @@ -388,6 +391,7 @@ type OnyxValues = { [ONYXKEYS.PERSONAL_DETAILS_LIST]: OnyxTypes.PersonalDetailsList; [ONYXKEYS.PRIVATE_PERSONAL_DETAILS]: OnyxTypes.PrivatePersonalDetails; [ONYXKEYS.TASK]: OnyxTypes.Task; + [ONYXKEYS.WORKSPACE_RATE_AND_UNIT]: OnyxTypes.WorkspaceRateAndUnit; [ONYXKEYS.CURRENCY_LIST]: Record; [ONYXKEYS.UPDATE_AVAILABLE]: boolean; [ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1ae99aa87500..f5fb7a45db98 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -461,21 +461,15 @@ const ROUTES = { }, WORKSPACE_RATE_AND_UNIT: { route: 'workspace/:policyID/rateandunit', - getRoute: (policyID: string, unit?: string, rate?: string) => - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - `workspace/${policyID}/rateandunit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit` as const, }, WORKSPACE_RATE_AND_UNIT_RATE: { route: 'workspace/:policyID/rateandunit/rate', - getRoute: (policyID: string, unit?: string, rate?: string) => - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - `workspace/${policyID}/rateandunit/rate${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/rate` as const, }, WORKSPACE_RATE_AND_UNIT_UNIT: { route: 'workspace/:policyID/rateandunit/unit', - getRoute: (policyID: string, unit?: string, rate?: string) => - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - `workspace/${policyID}/rateandunit/unit${unit || rate ? '?' : ''}${unit ? `unit=${unit}` : ''}${unit && rate ? '&' : ''}${rate ? `rate=${rate}` : ''}` as const, + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/unit` as const, }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', diff --git a/src/components/FormMenuItem.tsx b/src/components/FormMenuItem.tsx deleted file mode 100644 index 8ce81bb440c7..000000000000 --- a/src/components/FormMenuItem.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type {ForwardedRef} from 'react'; -import React, {forwardRef} from 'react'; -import type {View} from 'react-native'; -import type {AvatarProps, IconProps, MenuItemBaseProps, NoIcon} from './MenuItem'; -import MenuItem from './MenuItem'; - -type FormMenuItemProps = (NoIcon | AvatarProps | IconProps) & - Omit & { - /** A description text to show under the title provided by the FormProvider */ - value?: string; - - /** Custom value renderer to render description based on form values */ - customValueRenderer?: (value?: string) => string | undefined; - }; - -function FormMenuItem({customValueRenderer, value, errorText, ...props}: FormMenuItemProps, ref: ForwardedRef) { - return ( - - ); -} - -FormMenuItem.displayName = 'FormMenuItem'; - -export type {FormMenuItemProps}; -export default forwardRef(FormMenuItem); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index e17eaaa9426e..bf285fa02eba 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -1,23 +1,18 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import {ScrollView, View} from 'react-native'; +import Onyx, {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import FormMenuItem from '@components/FormMenuItem'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; 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 useThemeStyles from '@hooks/useThemeStyles'; 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'; @@ -27,25 +22,31 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; const propTypes = { - route: PropTypes.shape({ - params: PropTypes.shape({ - policyID: PropTypes.string.isRequired, - unit: PropTypes.string, - rate: PropTypes.string, - }).isRequired, - }).isRequired, + workspaceRateAndUnit: PropTypes.shape({ + policyID: PropTypes.string, + unit: PropTypes.string, + rate: PropTypes.string, + }), ...policyPropTypes, ...withLocalizePropTypes, - ...withThemeStylesPropTypes, }; const defaultProps = { + workspaceRateAndUnit: { + policyID: '', + }, reimbursementAccount: {}, ...policyDefaultProps, }; function WorkspaceRateAndUnitPage(props) { + const styles = useThemeStyles(); useEffect(() => { + if (!props.workspaceRateAndUnit.policyID || props.workspaceRateAndUnit.policyID !== props.policy.id) { + // TODO: Move this to a proper action + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy.id, rate: null, unit: null}); + } if (lodashGet(props, 'policy.customUnits', []).length !== 0) { return; } @@ -74,37 +75,25 @@ function WorkspaceRateAndUnitPage(props) { attributes: {unit}, rates: { ...currentCustomUnitRate, - rate, + rate: parseFloat(rate), }, }; Policy.updateWorkspaceCustomUnitAndRate(props.policy.id, distanceCustomUnit, newCustomUnit, props.policy.lastModified); }; - const submit = (values) => { - saveUnitAndRate(values.unit, values.rate); - Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy.id)); - }; - - const validate = (values) => { - const errors = {}; - const parsedRate = PolicyUtils.getRateDisplayValue(values.rate / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, props.toLocaleDigit); - 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(parsedRate) || parsedRate === '') { - errors.rate = 'workspace.reimburse.invalidRateError'; - } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 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); - const unitValue = props.route.params.unit || lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); - const rateValue = props.route.params.rate || distanceCustomRate.rate.toString(); + const unitValue = props.workspaceRateAndUnit.unit || lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + const rateValue = props.workspaceRateAndUnit.rate || distanceCustomRate.rate.toString(); + + const submit = () => { + saveUnitAndRate(unitValue, rateValue); + // TODO: Move this to a proper action + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, null); + Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy.id)); + }; return ( {() => ( - - - Policy.clearCustomUnitErrors(props.policy.id, lodashGet(distanceCustomUnit, 'customUnitID', ''), lodashGet(distanceCustomRate, 'customUnitRateID', '')) - } - > - {/* + + + Policy.clearCustomUnitErrors(props.policy.id, lodashGet(distanceCustomUnit, 'customUnitID', ''), lodashGet(distanceCustomRate, 'customUnitRateID', '')) + } + > + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.getRoute(props.policy.id))} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id))} + shouldShowRightIcon + /> + + + + + submit()} + enabledWhenOffline + buttonText={props.translate('common.save')} + containerStyles={[styles.mh0, styles.mt5, styles.flex1, styles.ph5]} /> - - - - */} - CurrencyUtils.convertAmountToDisplayString(value, lodashGet(props, 'policy.outputCurrency', CONST.CURRENCY.USD))} - shouldShowRightIcon - onPress={() => { - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.getRoute(props.policy.id, unitValue, rateValue)); - }} - /> - unitItems[value]} - shouldShowRightIcon - onPress={() => { - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id, unitValue, rateValue)); - }} - /> - - + + )} ); @@ -200,6 +167,8 @@ export default compose( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, + workspaceRateAndUnit: { + key: ONYXKEYS.WORKSPACE_RATE_AND_UNIT, + }, }), - withThemeStyles, )(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 75721d4ae55b..1bba65da1bb3 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -1,48 +1,68 @@ -import type {RouteProp} from '@react-navigation/native'; -import React, {useMemo} from 'react'; +import React, {useMemo, useEffect} from 'react'; +import Onyx, {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; import type {OnyxFormValuesFields} from '@components/Form/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +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 from '@pages/workspace/withPolicy'; -import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {WorkspaceRateAndUnit} from '@src/types/onyx'; -type WorkspaceUnitPageProps = WithPolicyOnyxProps & { - route: RouteProp<{params: {policyID: string; unit?: string; rate?: string}}>; +type WorkspaceRatePageBaseProps = WithPolicyProps; + +type WorkspaceRateAndUnitOnyxProps = { + workspaceRateAndUnit: OnyxEntry; }; -function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { +type WorkspaceRatePageProps = WorkspaceRatePageBaseProps & WorkspaceRateAndUnitOnyxProps; + +function WorkspaceRatePage(props: WorkspaceRatePageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); + useEffect(() => { + if (props.workspaceRateAndUnit?.policyID === props.policy?.id) { + return; + } + // TODO: Move this to a action later. + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy?.id, rate: null, unit: null}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const submit = (values: OnyxFormValuesFields) => { - const rateEdit = values.rateEdit as number; - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', props.route.params.unit, (rateEdit * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString())); + const rate = values.rate as number; + // TODO: Move this to a action later. + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {rate: (rate * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()}); + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; const validate = (values: OnyxFormValuesFields) => { - const errors: {rateEdit?: string} = {}; - const rateEdit = values.rateEdit as number; - const parsedRate = PolicyUtils.getRateDisplayValue(rateEdit, toLocaleDigit); + const errors: {rate?: string} = {}; + const rate = values.rate as number; + const parsedRate = PolicyUtils.getRateDisplayValue(rate, toLocaleDigit); const decimalSeparator = toLocaleDigit('.'); const outputCurrency = 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(parsedRate) || parsedRate === '') { - errors.rateEdit = 'workspace.reimburse.invalidRateError'; + errors.rate = 'workspace.reimburse.invalidRateError'; } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { - errors.rateEdit = 'workspace.reimburse.lowRateError'; + errors.rate = 'workspace.reimburse.lowRateError'; } return errors; }; @@ -76,9 +96,11 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { > )} @@ -86,6 +108,13 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { ); } -WorkspaceUnitPage.displayName = 'WorkspaceUnitPage'; +WorkspaceRatePage.displayName = 'WorkspaceRatePage'; -export default withPolicy(WorkspaceUnitPage); +export default compose( + withOnyx({ + workspaceRateAndUnit: { + key: ONYXKEYS.WORKSPACE_RATE_AND_UNIT, + }, + }), + withPolicy, +)(WorkspaceRatePage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index 95d3644f486a..c758d2d19ef2 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -1,15 +1,19 @@ -import type {RouteProp} from '@react-navigation/native'; -import React, {useMemo} from 'react'; +import React, {useEffect, useMemo} from 'react'; +import Onyx, {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import withPolicy from '@pages/workspace/withPolicy'; -import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {WorkspaceRateAndUnit} from '@src/types/onyx'; type OptionRow = { value: string; @@ -18,10 +22,13 @@ type OptionRow = { isSelected: boolean; }; -type WorkspaceUnitPageProps = WithPolicyOnyxProps & { - route: RouteProp<{params: {policyID: string; unit?: string; rate?: string}}>; +type WorkspaceUnitPageBaseProps = WithPolicyProps; + +type WorkspaceRateAndUnitOnyxProps = { + workspaceRateAndUnit: OnyxEntry; }; +type WorkspaceUnitPageProps = WorkspaceUnitPageBaseProps & WorkspaceRateAndUnitOnyxProps; function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -33,8 +40,21 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { [translate], ); + useEffect(() => { + if (props.workspaceRateAndUnit?.policyID === props.policy?.id) { + return; + } + // TODO: Move this to a action later. + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy?.id, rate: null, unit: null}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const updateUnit = (unit: string) => { - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '', unit, props.route.params.rate)); + // TODO: Move this to a action later. + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {unit}); + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; const defaultValue = useMemo(() => { @@ -50,11 +70,11 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { text: label, keyForList: unit, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - isSelected: (props.route.params.unit || defaultValue) === unit, + isSelected: (props.workspaceRateAndUnit?.unit || defaultValue) === unit, }); }); return arr; - }, [defaultValue, props.route.params.unit, unitItems]); + }, [defaultValue, props.workspaceRateAndUnit?.unit, unitItems]); return ( ({ + workspaceRateAndUnit: { + key: ONYXKEYS.WORKSPACE_RATE_AND_UNIT, + }, + }), + withPolicy, +)(WorkspaceUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index 23136064fc2b..9a17e5ee87e0 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -2,7 +2,7 @@ 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 Onyx, {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import CopyTextToClipboard from '@components/CopyTextToClipboard'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -146,7 +146,12 @@ function WorkspaceReimburseView(props) { title={currentRatePerUnit} description={translate('workspace.reimburse.trackDistanceRate')} shouldShowRightIcon - onPress={() => Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy.id))} + onPress={() => { + // TODO: Make this a proper action + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy.id, rate: null, unit: null}); + Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy.id)); + }} wrapperStyle={[styles.mhn5, styles.wAuto]} brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} /> diff --git a/src/types/onyx/WorkspaceRateAndUnit.ts b/src/types/onyx/WorkspaceRateAndUnit.ts new file mode 100644 index 000000000000..bddc9933ab75 --- /dev/null +++ b/src/types/onyx/WorkspaceRateAndUnit.ts @@ -0,0 +1,12 @@ +type WorkspaceRateAndUnit = { + /** policyID of the Workspace */ + policyID: string; + + /** Unit of the Workspace */ + unit?: string; + + /** Unit of the Workspace */ + rate?: string; +}; + +export default WorkspaceRateAndUnit; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 5b04cae58671..08a461d46185 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -68,6 +68,7 @@ import type WalletOnfido from './WalletOnfido'; import type WalletStatement from './WalletStatement'; import type WalletTerms from './WalletTerms'; import type WalletTransfer from './WalletTransfer'; +import type WorkspaceRateAndUnit from './WorkspaceRateAndUnit'; export type { Account, @@ -143,6 +144,7 @@ export type { WalletStatement, WalletTerms, WalletTransfer, + WorkspaceRateAndUnit, ReportUserIsTyping, PolicyReportField, PolicyReportFields, From eb92a0689c5809ebdddc9fe5252ec839e9a00903 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 2 Feb 2024 18:26:26 +0530 Subject: [PATCH 14/35] merge main --- .../linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 8 +++++++- .../reimburse/WorkspaceRateAndUnitPage/BasePage.js | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index d61b36871434..6f5933a1497b 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -3,7 +3,7 @@ import SCREENS from '@src/SCREENS'; const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = { [SCREENS.WORKSPACE.OVERVIEW]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY], - [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT], + [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT], [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE], }; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index f1c9c316fe93..3f359eb756f8 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -230,9 +230,15 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CURRENCY]: { path: ROUTES.WORKSPACE_OVERVIEW_CURRENCY.route, }, - [SCREENS.WORKSPACE.RATE_AND_UNIT]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: { path: ROUTES.WORKSPACE_RATE_AND_UNIT.route, }, + [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: { + path: ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.route, + }, + [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: { + path: ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.route, + }, [SCREENS.WORKSPACE.INVITE]: { path: ROUTES.WORKSPACE_INVITE.route, }, diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index bf285fa02eba..ba493669350b 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -92,7 +92,7 @@ function WorkspaceRateAndUnitPage(props) { // TODO: Move this to a proper action // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, null); - Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy.id)); + Navigation.goBack(); }; return ( @@ -101,8 +101,8 @@ function WorkspaceRateAndUnitPage(props) { route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} shouldSkipVBBACall - backButtonRoute={ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy.id)} shouldShowLoading={false} + shouldShowBackButton > {() => ( Date: Fri, 2 Feb 2024 18:41:51 +0530 Subject: [PATCH 15/35] minor fixes --- .../Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- .../workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 1 + .../workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 6f5933a1497b..6af65faf16cf 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -3,7 +3,7 @@ import SCREENS from '@src/SCREENS'; const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = { [SCREENS.WORKSPACE.OVERVIEW]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY], - [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT], + [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT, SCREENS.WORKSPACE.RATE_AND_UNIT.RATE, SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT], [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE], }; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 1bba65da1bb3..dbe6606809c1 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -81,6 +81,7 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { shouldSkipVBBACall backButtonRoute={ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')} shouldShowLoading={false} + shouldShowBackButton > {() => ( {() => ( <> From efea85344ffc34643a8e15d1de94fed018eb0831 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 2 Feb 2024 18:54:43 +0530 Subject: [PATCH 16/35] type fixes --- src/SCREENS.ts | 8 +++----- src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 6 +++--- .../linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 6 +++--- src/libs/Navigation/types.ts | 6 +++--- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 14fe7fb0f5e9..d48cc29e01e7 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -203,11 +203,9 @@ const SCREENS = { OVERVIEW: 'Workspace_Overview', CARD: 'Workspace_Card', REIMBURSE: 'Workspace_Reimburse', - RATE_AND_UNIT: { - ROOT: 'Workspace_RateAndUnit_Root', - RATE: 'Workspace_RateAndUnit_Rate', - UNIT: 'Workspace_RateAndUnit_Unit', - }, + RATE_AND_UNIT: 'Workspace_RateAndUnit', + RATE_AND_UNIT_RATE: 'Workspace_RateAndUnit_Rate', + RATE_AND_UNIT_UNIT: 'Workspace_RateAndUnit_Unit', BILLS: 'Workspace_Bills', INVOICES: 'Workspace_Invoices', TRAVEL: 'Workspace_Travel', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 27534cefab48..84fd7c3d135e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -235,9 +235,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../pages/settings/Profile/CustomStatus/SetDatePage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: () => require('../../../pages/settings/Profile/CustomStatus/SetTimePage').default as React.ComponentType, - [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage').default as React.ComponentType, - [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType, - [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default as React.ComponentType, [SCREENS.WORKSPACE.INVITE]: () => require('../../../pages/workspace/WorkspaceInvitePage').default as React.ComponentType, [SCREENS.WORKSPACE.INVITE_MESSAGE]: () => require('../../../pages/workspace/WorkspaceInviteMessagePage').default as React.ComponentType, [SCREENS.WORKSPACE.NAME]: () => require('../../../pages/workspace/WorkspaceNamePage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 6af65faf16cf..e8508510b8a2 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -3,7 +3,7 @@ import SCREENS from '@src/SCREENS'; const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = { [SCREENS.WORKSPACE.OVERVIEW]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY], - [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT, SCREENS.WORKSPACE.RATE_AND_UNIT.RATE, SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT], + [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT], [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE], }; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 3f359eb756f8..59048584e24b 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -230,13 +230,13 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CURRENCY]: { path: ROUTES.WORKSPACE_OVERVIEW_CURRENCY.route, }, - [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT]: { path: ROUTES.WORKSPACE_RATE_AND_UNIT.route, }, - [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: { path: ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.route, }, - [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: { path: ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.route, }, [SCREENS.WORKSPACE.INVITE]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 06b8f00e8857..0409fb5c5612 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -125,13 +125,13 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: undefined; [SCREENS.WORKSPACE.CURRENCY]: undefined; [SCREENS.WORKSPACE.NAME]: undefined; - [SCREENS.WORKSPACE.RATE_AND_UNIT.ROOT]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT]: { policyID: string; }; - [SCREENS.WORKSPACE.RATE_AND_UNIT.RATE]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: { policyID: string; }; - [SCREENS.WORKSPACE.RATE_AND_UNIT.UNIT]: { + [SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: { policyID: string; }; [SCREENS.WORKSPACE.INVITE]: { From c1bbbc2701087546ac56d58550c88dd161637230 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 2 Feb 2024 20:16:13 +0530 Subject: [PATCH 17/35] prettier fix --- .../workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 4 ++-- .../workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index dbe6606809c1..139ae3cb1d46 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useEffect} from 'react'; +import React, {useEffect, useMemo} from 'react'; import Onyx, {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; @@ -40,7 +40,7 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { // TODO: Move this to a action later. // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy?.id, rate: null, unit: null}); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const submit = (values: OnyxFormValuesFields) => { diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index c2eca9a318a6..8fdf81a99b02 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -47,7 +47,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { // TODO: Move this to a action later. // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy?.id, rate: null, unit: null}); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const updateUnit = (unit: string) => { From c8cfca208d8d743bca390a4f88d6d458f70a4096 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sat, 3 Feb 2024 19:34:02 +0530 Subject: [PATCH 18/35] fixed error with changing value in AmountForm --- src/components/AmountForm.tsx | 38 +++++++------------ src/components/Form/FormProvider.tsx | 4 +- src/components/Form/types.ts | 4 +- src/libs/PolicyUtils.ts | 1 - .../WorkspaceRateAndUnitPage/RatePage.tsx | 14 +++---- src/types/onyx/Form.ts | 2 +- 6 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index df3256679074..88ea67881258 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {forwardRef, useCallback, useEffect, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -16,7 +16,7 @@ import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; type AmountFormProps = { /** Amount supplied by the FormProvider */ - value?: number; + value?: string; /** Currency supplied by user */ currency?: string; @@ -25,7 +25,7 @@ type AmountFormProps = { errorText?: string; /** Callback to update the amount in the FormProvider */ - onInputChange?: (value: number) => void; + onInputChange?: (value: string) => void; /** Fired when back button pressed, navigates to currency selection page */ onCurrencyButtonPress?: () => void; @@ -43,21 +43,20 @@ const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; -function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText, onInputChange, onCurrencyButtonPress}: AmountFormProps, forwardedRef: ForwardedRef) { +function AmountForm({value: amount, currency = CONST.CURRENCY.USD, errorText, onInputChange, onCurrencyButtonPress}: AmountFormProps, forwardedRef: ForwardedRef) { const styles = useThemeStyles(); const {toLocaleDigit, numberFormat} = useLocalize(); const textInput = useRef(null); const decimals = CurrencyUtils.getCurrencyDecimals(currency); - const selectedAmountAsString = amount.toString(); + const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]); - const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); const [selection, setSelection] = useState({ - start: selectedAmountAsString.length, - end: selectedAmountAsString.length, + start: currentAmount.length, + end: currentAmount.length, }); const forwardDeletePressedRef = useRef(false); @@ -95,23 +94,12 @@ function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText return; } - // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to - // setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet - // flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. - - let hasSelectionBeenSet = false; - setCurrentAmount((prevAmount) => { - const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); - const isForwardDelete = prevAmount.length > strippedAmount.length && forwardDeletePressedRef.current; - if (!hasSelectionBeenSet) { - hasSelectionBeenSet = true; - setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); - } - onInputChange?.(parseFloat(strippedAmount)); - return strippedAmount; - }); + const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(newAmountWithoutSpaces); + const isForwardDelete = currentAmount.length > strippedAmount.length && forwardDeletePressedRef.current; + setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : currentAmount.length, strippedAmount.length)); + onInputChange?.(strippedAmount); }, - [decimals, onInputChange], + [currentAmount, decimals, onInputChange], ); // Modifies the amount to match the decimals for changed currency. @@ -126,7 +114,7 @@ function AmountForm({value: amount = 0, currency = CONST.CURRENCY.USD, errorText // we want to update only when decimals change (setNewAmount also changes when decimals change). // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setNewAmount]); + }, [decimals]); /** * Update amount with number or Backspace pressed for BigNumberPad. diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index fd1377bb84a1..424fd989291a 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -22,7 +22,7 @@ import type {BaseInputProps, FormProps, InputRefs, OnyxFormKeyWithoutDraft, Onyx // More details: https://github.com/Expensify/App/pull/16444#issuecomment-1482983426 const VALIDATE_DELAY = 200; -type InitialDefaultValue = false | Date | '' | 0; +type InitialDefaultValue = false | Date | ''; function getInitialValueByType(valueType?: ValueTypeKey): InitialDefaultValue { switch (valueType) { @@ -32,8 +32,6 @@ function getInitialValueByType(valueType?: ValueTypeKey): InitialDefaultValue { return false; case 'date': return new Date(); - case 'number': - return 0; default: return ''; } diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index 4e7d74baa343..4decec74c2dc 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -16,11 +16,11 @@ import type {BaseForm, FormValueType} from '@src/types/onyx/Form'; * when adding new inputs or removing old ones. * * TODO: Add remaining inputs here once these components are migrated to Typescript: - * CountrySelector | StatePicker | DatePicker | EmojiPickerButtonDropdown | RoomNameInput | ValuePicker | FormMenuItem + * CountrySelector | StatePicker | DatePicker | EmojiPickerButtonDropdown | RoomNameInput | ValuePicker */ type ValidInputs = typeof TextInput | typeof AmountTextInput | typeof SingleChoiceQuestion | typeof CheckboxWithLabel | typeof Picker | typeof AddressSearch | typeof AmountForm; -type ValueTypeKey = 'string' | 'boolean' | 'date' | 'number'; +type ValueTypeKey = 'string' | 'boolean' | 'date'; type MeasureLayoutOnSuccessCallback = (left: number, top: number, width: number, height: number) => void; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 531f960dce47..b6ee4ab3a353 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -231,7 +231,6 @@ export { hasCustomUnitsError, getNumericValue, getUnitRateValue, - getRateDisplayValue, getPolicyBrickRoadIndicatorStatus, shouldShowPolicy, isExpensifyTeam, diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 139ae3cb1d46..a070fca063c7 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -10,9 +10,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; +import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; 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'; @@ -44,17 +44,17 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { }, []); const submit = (values: OnyxFormValuesFields) => { - const rate = values.rate as number; + const rate = values.rate as string; // TODO: Move this to a action later. // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {rate: (rate * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()}); + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {rate: (parseFloat(rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()}); Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; const validate = (values: OnyxFormValuesFields) => { const errors: {rate?: string} = {}; - const rate = values.rate as number; - const parsedRate = PolicyUtils.getRateDisplayValue(rate, toLocaleDigit); + const rate = values.rate as string; + const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); const decimalSeparator = toLocaleDigit('.'); const outputCurrency = props.policy?.outputCurrency ?? CONST.CURRENCY.USD; // Allow one more decimal place for accuracy @@ -99,9 +99,9 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { InputComponent={AmountForm} inputID="rate" currency={props.policy?.outputCurrency ?? CONST.CURRENCY.USD} - defaultValue={ + defaultValue={( (typeof props.workspaceRateAndUnit?.rate === 'string' ? parseFloat(props.workspaceRateAndUnit.rate) : defaultValue) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET - } + ).toString()} /> )} diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index eaf91e99f205..3235f340e723 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -1,7 +1,7 @@ import type * as OnyxCommon from './OnyxCommon'; import type PersonalBankAccount from './PersonalBankAccount'; -type FormValueType = string | boolean | number | Date | OnyxCommon.Errors; +type FormValueType = string | boolean | Date | OnyxCommon.Errors; type BaseForm = { /** Controls the loading state of the form */ From ecc6de6223499d0db9e62f4229b200d7cfcb8e09 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 4 Feb 2024 17:49:53 +0530 Subject: [PATCH 19/35] fix translations --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ .../workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js | 4 ++-- .../workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 5556bd0666db..d246c41b7780 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1610,6 +1610,8 @@ export default { trackDistanceUnit: 'Unit', trackDistanceUnitTitle: 'Track distance: Unit', trackDistanceChooseUnit: 'Choose a default unit to track.', + kilometers: 'Kilometers', + miles: 'Miles', unlockNextDayReimbursements: 'Unlock next-day reimbursements', captureNoVBACopyBeforeEmail: 'Ask your workspace members to forward receipts to ', captureNoVBACopyAfterEmail: ' and download the Expensify App to track cash expenses on the go.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 110f2165bbc9..b9f1094f92c0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1633,6 +1633,8 @@ export default { trackDistanceUnit: 'Unidad', trackDistanceUnitTitle: 'Medir distancia: Unidad', trackDistanceChooseUnit: 'Elija una unidad predeterminada para rastrear.', + kilometers: 'Kilómetros', + miles: 'Millas', unlockNextDayReimbursements: 'Desbloquea reembolsos diarios', captureNoVBACopyBeforeEmail: 'Pide a los miembros de tu espacio de trabajo que envíen recibos a ', captureNoVBACopyAfterEmail: ' y descarga la App de Expensify para controlar tus gastos en efectivo sobre la marcha.', diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index ba493669350b..337bb6a51cd7 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -56,8 +56,8 @@ function WorkspaceRateAndUnitPage(props) { }, [props]); const unitItems = { - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: props.translate('common.kilometers'), - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: props.translate('common.miles'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: props.translate('workspace.reimburse.kilometers'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: props.translate('workspace.reimburse.miles'), }; const saveUnitAndRate = (unit, rate) => { diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index 8fdf81a99b02..31f5da1f8347 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -34,8 +34,8 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { const {translate} = useLocalize(); const unitItems = useMemo( () => ({ - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: translate('common.kilometers'), - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: translate('common.miles'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: translate('workspace.reimburse.kilometers'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: translate('workspace.reimburse.miles'), }), [translate], ); From bbf64b1267a2ee4bbb88a4c1091f7d3691838338 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 4 Feb 2024 18:33:55 +0530 Subject: [PATCH 20/35] move onyx to actions --- src/libs/actions/Policy.ts | 20 +++++++++++++++++++ .../WorkspaceRateAndUnitPage/BasePage.js | 10 +++------- .../WorkspaceRateAndUnitPage/RatePage.tsx | 11 ++++------ .../WorkspaceRateAndUnitPage/UnitPage.tsx | 11 ++++------ 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 0c3a8afc1576..11752e22d634 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1449,6 +1449,22 @@ function openWorkspaceReimburseView(policyID: string) { API.read(READ_COMMANDS.OPEN_WORKSPACE_REIMBURSE_VIEW, params, {successData, failureData}); } +function setPolicyIDForReimburseView(policyID: string) { + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID, rate: null, unit: null}); +} + +function clearOnyxDataForReimburseView() { + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, null); +} + +function setRateForReimburseView(rate: string) { + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {rate}); +} + +function setUnitForReimburseView(unit: string) { + Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {unit}); +} + /** * Returns the accountIDs of the members of the policy whose data is passed in the parameters */ @@ -1999,6 +2015,10 @@ export { clearAddMemberError, clearDeleteWorkspaceError, openWorkspaceReimburseView, + setPolicyIDForReimburseView, + clearOnyxDataForReimburseView, + setRateForReimburseView, + setUnitForReimburseView, generateDefaultWorkspaceName, updateGeneralSettings, clearWorkspaceGeneralSettingsErrors, diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js index 337bb6a51cd7..4483fc935fd7 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js @@ -2,7 +2,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; import {ScrollView, View} from 'react-native'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -43,9 +43,7 @@ function WorkspaceRateAndUnitPage(props) { const styles = useThemeStyles(); useEffect(() => { if (!props.workspaceRateAndUnit.policyID || props.workspaceRateAndUnit.policyID !== props.policy.id) { - // TODO: Move this to a proper action - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy.id, rate: null, unit: null}); + Policy.setPolicyIDForReimburseView(props.policy.id); } if (lodashGet(props, 'policy.customUnits', []).length !== 0) { return; @@ -89,9 +87,7 @@ function WorkspaceRateAndUnitPage(props) { const submit = () => { saveUnitAndRate(unitValue, rateValue); - // TODO: Move this to a proper action - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, null); + Policy.clearOnyxDataForReimburseView(); Navigation.goBack(); }; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index a070fca063c7..cbc9a3f72e11 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useMemo} from 'react'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; @@ -16,6 +16,7 @@ import * as NumberUtils from '@libs/NumberUtils'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -37,17 +38,13 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { if (props.workspaceRateAndUnit?.policyID === props.policy?.id) { return; } - // TODO: Move this to a action later. - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy?.id, rate: null, unit: null}); + Policy.setPolicyIDForReimburseView(props.policy?.id ?? ''); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const submit = (values: OnyxFormValuesFields) => { const rate = values.rate as string; - // TODO: Move this to a action later. - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {rate: (parseFloat(rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()}); + Policy.setRateForReimburseView((parseFloat(rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()); Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index 31f5da1f8347..d5f8d4f192b3 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useMemo} from 'react'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import Text from '@components/Text'; @@ -10,6 +10,7 @@ import Navigation from '@libs/Navigation/Navigation'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -44,16 +45,12 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { if (props.workspaceRateAndUnit?.policyID === props.policy?.id) { return; } - // TODO: Move this to a action later. - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy?.id, rate: null, unit: null}); + Policy.setPolicyIDForReimburseView(props.policy?.id ?? ''); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const updateUnit = (unit: string) => { - // TODO: Move this to a action later. - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {unit}); + Policy.setUnitForReimburseView(unit); Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; From 6c8601113b7c0681837303df21fb4ebe14b75876 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 4 Feb 2024 19:34:32 +0530 Subject: [PATCH 21/35] convert initial page to ts --- .../AppNavigator/ModalStackNavigators.tsx | 2 +- src/libs/actions/Policy.ts | 4 +- .../{BasePage.js => InitialPage.tsx} | 125 +++++++++--------- .../WorkspaceRateAndUnitPage/UnitPage.tsx | 7 +- .../reimburse/WorkspaceReimburseView.js | 6 +- src/types/onyx/Policy.ts | 4 +- src/types/onyx/WorkspaceRateAndUnit.ts | 4 +- 7 files changed, 79 insertions(+), 73 deletions(-) rename src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/{BasePage.js => InitialPage.tsx} (50%) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 84fd7c3d135e..40c1036167bd 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -235,7 +235,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../pages/settings/Profile/CustomStatus/SetDatePage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: () => require('../../../pages/settings/Profile/CustomStatus/SetTimePage').default as React.ComponentType, - [SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage').default as React.ComponentType, + [SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage').default as React.ComponentType, [SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType, [SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default as React.ComponentType, [SCREENS.WORKSPACE.INVITE]: () => require('../../../pages/workspace/WorkspaceInvitePage').default as React.ComponentType, diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 11752e22d634..7aa950a4cd23 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 {CustomUnit, Unit} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AnnounceRoomMembersOnyxData = { @@ -1461,7 +1461,7 @@ function setRateForReimburseView(rate: string) { Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {rate}); } -function setUnitForReimburseView(unit: string) { +function setUnitForReimburseView(unit: Unit) { Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {unit}); } diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx similarity index 50% rename from src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js rename to src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx index 4483fc935fd7..eb74e02a2415 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/BasePage.js +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx @@ -1,89 +1,98 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; +import React, {useEffect, useMemo} from 'react'; import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {withNetwork} from '@components/OnyxProvider'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; -import withPolicy, {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy'; +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 {Network, ReimbursementAccount, WorkspaceRateAndUnit} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; -const propTypes = { - workspaceRateAndUnit: PropTypes.shape({ - policyID: PropTypes.string, - unit: PropTypes.string, - rate: PropTypes.string, - }), - ...policyPropTypes, - ...withLocalizePropTypes, +type WorkspaceRateAndUnitPageBaseProps = WithPolicyProps & { + // eslint-disable-next-line react/no-unused-prop-types + network: OnyxEntry; }; -const defaultProps = { - workspaceRateAndUnit: { - policyID: '', - }, - reimbursementAccount: {}, - ...policyDefaultProps, +type WorkspaceRateAndUnitOnyxProps = { + workspaceRateAndUnit: OnyxEntry; + // eslint-disable-next-line react/no-unused-prop-types + reimbursementAccount: OnyxEntry; }; -function WorkspaceRateAndUnitPage(props) { +type WorkspaceRateAndUnitPageProps = WorkspaceRateAndUnitPageBaseProps & WorkspaceRateAndUnitOnyxProps; + +function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); + useEffect(() => { - if (!props.workspaceRateAndUnit.policyID || props.workspaceRateAndUnit.policyID !== props.policy.id) { - Policy.setPolicyIDForReimburseView(props.policy.id); + if (props.workspaceRateAndUnit?.policyID === props.policy?.id) { + return; } - if (lodashGet(props, 'policy.customUnits', []).length !== 0) { + Policy.setPolicyIDForReimburseView(props.policy?.id ?? ''); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const customUnits = props.policy?.customUnits ?? {}; + if (!isEmptyObject(customUnits)) { return; } BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(props.policy.id); + Policy.openWorkspaceReimburseView(props.policy?.id ?? ''); }, [props]); - const unitItems = { - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: props.translate('workspace.reimburse.kilometers'), - [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: props.translate('workspace.reimburse.miles'), - }; + const unitItems = useMemo( + () => ({ + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]: translate('workspace.reimburse.kilometers'), + [CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES]: translate('workspace.reimburse.miles'), + }), + [translate], + ); - const saveUnitAndRate = (unit, rate) => { - const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), (customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const saveUnitAndRate = (newUnit: Unit, newRate: string) => { + const distanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.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 currentCustomUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const unitID = distanceCustomUnit.customUnitID ?? ''; + const unitName = distanceCustomUnit.name ?? ''; const newCustomUnit = { customUnitID: unitID, name: unitName, - attributes: {unit}, + attributes: {unit: newUnit}, rates: { ...currentCustomUnitRate, - rate: parseFloat(rate), + rate: parseFloat(newRate), }, }; - Policy.updateWorkspaceCustomUnitAndRate(props.policy.id, distanceCustomUnit, newCustomUnit, props.policy.lastModified); + // @ts-expect-error Need to consult here. + Policy.updateWorkspaceCustomUnitAndRate(props.policy?.id ?? '', distanceCustomUnit, newCustomUnit, props.policy?.lastModified); }; - 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 distanceCustomUnit = Object.values(props.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 unitValue = props.workspaceRateAndUnit.unit || lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); - const rateValue = props.workspaceRateAndUnit.rate || distanceCustomRate.rate.toString(); + const unitValue = props.workspaceRateAndUnit?.unit ?? distanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; + const rateValue = props.workspaceRateAndUnit?.rate ?? distanceCustomRate?.rate?.toString() ?? ''; const submit = () => { saveUnitAndRate(unitValue, rateValue); @@ -93,7 +102,7 @@ function WorkspaceRateAndUnitPage(props) { return ( - Policy.clearCustomUnitErrors(props.policy.id, lodashGet(distanceCustomUnit, 'customUnitID', ''), lodashGet(distanceCustomRate, 'customUnitRateID', '')) - } + pendingAction={distanceCustomUnit?.pendingAction ?? distanceCustomRate?.pendingAction} + onClose={() => Policy.clearCustomUnitErrors(props.policy?.id ?? '', distanceCustomUnit?.customUnitID ?? '', distanceCustomRate?.customUnitRateID ?? '')} > Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.getRoute(props.policy.id))} + description={translate('workspace.reimburse.trackDistanceRate')} + title={CurrencyUtils.convertAmountToDisplayString(parseFloat(rateValue), props.policy?.outputCurrency ?? CONST.CURRENCY.USD)} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_RATE.getRoute(props.policy?.id ?? ''))} shouldShowRightIcon /> Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy.id))} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT_UNIT.getRoute(props.policy?.id ?? ''))} shouldShowRightIcon /> @@ -141,8 +148,9 @@ function WorkspaceRateAndUnitPage(props) { submit()} enabledWhenOffline - buttonText={props.translate('common.save')} + buttonText={translate('common.save')} containerStyles={[styles.mh0, styles.mt5, styles.flex1, styles.ph5]} + isAlertVisible={false} /> @@ -151,15 +159,10 @@ function WorkspaceRateAndUnitPage(props) { ); } -WorkspaceRateAndUnitPage.propTypes = propTypes; -WorkspaceRateAndUnitPage.defaultProps = defaultProps; WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage'; export default compose( - withPolicy, - withLocalize, - withNetwork(), - withOnyx({ + withOnyx({ reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, @@ -167,4 +170,6 @@ export default compose( key: ONYXKEYS.WORKSPACE_RATE_AND_UNIT, }, }), + withPolicy, + withNetwork(), )(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index d5f8d4f192b3..8b37a2f9c0c7 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -15,9 +15,10 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {WorkspaceRateAndUnit} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; type OptionRow = { - value: string; + value: Unit; text: string; keyForList: string; isSelected: boolean; @@ -49,7 +50,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const updateUnit = (unit: string) => { + const updateUnit = (unit: Unit) => { Policy.setUnitForReimburseView(unit); Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; @@ -63,7 +64,7 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { const arr: OptionRow[] = []; Object.entries(unitItems).forEach(([unit, label]) => { arr.push({ - value: unit, + value: unit as Unit, text: label, keyForList: unit, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index 33a27ef1c3e2..e01c161cf825 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -2,7 +2,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import CopyTextToClipboard from '@components/CopyTextToClipboard'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -151,9 +151,7 @@ function WorkspaceReimburseView(props) { description={translate('workspace.reimburse.trackDistanceRate')} shouldShowRightIcon onPress={() => { - // TODO: Make this a proper action - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(ONYXKEYS.WORKSPACE_RATE_AND_UNIT, {policyID: props.policy.id, rate: null, unit: null}); + Policy.setPolicyIDForReimburseView(props.policy.id); Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy.id)); }} wrapperStyle={[styles.mt3, styles.ph8, styles.mhn8, styles.wAuto]} diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index f55b3b797bf0..83755d48b8df 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; }; diff --git a/src/types/onyx/WorkspaceRateAndUnit.ts b/src/types/onyx/WorkspaceRateAndUnit.ts index bddc9933ab75..a374239c93f8 100644 --- a/src/types/onyx/WorkspaceRateAndUnit.ts +++ b/src/types/onyx/WorkspaceRateAndUnit.ts @@ -1,9 +1,11 @@ +type Unit = 'mi' | 'km'; + type WorkspaceRateAndUnit = { /** policyID of the Workspace */ policyID: string; /** Unit of the Workspace */ - unit?: string; + unit?: Unit; /** Unit of the Workspace */ rate?: string; From d442ef5ac7ff8cbe24e5b054488a4257ec0c4703 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 4 Feb 2024 19:45:07 +0530 Subject: [PATCH 22/35] improving comments --- src/components/AmountForm.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 88ea67881258..01d5c3e3e75f 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -80,7 +80,7 @@ function AmountForm({value: amount, currency = CONST.CURRENCY.USD, errorText, on /** * Sets the selection and the amount accordingly to the value passed to the input - * @param {String} newAmount - Changed amount from user input + * @param newAmount - Changed amount from user input */ const setNewAmount = useCallback( (newAmount: string) => { @@ -119,8 +119,6 @@ function AmountForm({value: amount, currency = CONST.CURRENCY.USD, errorText, on /** * Update amount with number or Backspace pressed for BigNumberPad. * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit to enable Next button - * - * @param {String} key */ const updateAmountNumberPad = useCallback( (key: string) => { @@ -145,7 +143,7 @@ function AmountForm({value: amount, currency = CONST.CURRENCY.USD, errorText, on /** * Update long press value, to remove items pressing on < * - * @param {Boolean} value - Changed text from user input + * @param value - Changed text from user input */ const updateLongPressHandlerState = useCallback((value: boolean) => { setShouldUpdateSelection(!value); From b07ed293d1499b82e2b3d7a6a328eb57803c75cd Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 4 Feb 2024 19:58:46 +0530 Subject: [PATCH 23/35] fix type bug --- .../UpdateWorkspaceCustomUnitAndRateParams.ts | 2 +- src/libs/actions/Policy.ts | 17 ++++++++++++----- .../WorkspaceRateAndUnitPage/InitialPage.tsx | 1 - src/types/onyx/Policy.ts | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts index 22bbd20c7308..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; + lastModified?: string; customUnit: string; customUnitRate: string; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 7aa950a4cd23..0bcf23bd4040 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, Unit} from '@src/types/onyx/Policy'; +import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AnnounceRoomMembersOnyxData = { @@ -69,6 +69,13 @@ type OptimisticCustomUnits = { type PoliciesRecord = Record>; +type NewCustomUnit = { + customUnitID: string; + name: string; + attributes: Attributes; + rates: Rate; +}; + const allPolicies: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, @@ -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?: string) { 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'), }, diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx index eb74e02a2415..ba925ea968ee 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx @@ -84,7 +84,6 @@ function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { rate: parseFloat(newRate), }, }; - // @ts-expect-error Need to consult here. Policy.updateWorkspaceCustomUnitAndRate(props.policy?.id ?? '', distanceCustomUnit, newCustomUnit, props.policy?.lastModified); }; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 83755d48b8df..c3a259becf04 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -165,4 +165,4 @@ type Policy = { export default Policy; -export type {Unit, CustomUnit}; +export type {Unit, CustomUnit, Attributes, Rate}; From e091da5f9a3cf5e27ab3f5e75ff7d2d5387cef2f Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 4 Feb 2024 20:09:04 +0530 Subject: [PATCH 24/35] allowed extra decimal for get accurate rate --- src/components/AmountForm.tsx | 7 +++++-- .../reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 01d5c3e3e75f..9603ecaba46d 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -21,6 +21,9 @@ type AmountFormProps = { /** Currency supplied by user */ currency?: string; + /** Tells how many extra decimal digits are allowed. Default is 0. */ + extraDecimals?: number; + /** Error to display at the bottom of the component */ errorText?: string; @@ -43,13 +46,13 @@ const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; -function AmountForm({value: amount, currency = CONST.CURRENCY.USD, errorText, onInputChange, onCurrencyButtonPress}: AmountFormProps, forwardedRef: ForwardedRef) { +function AmountForm({value: amount, currency = CONST.CURRENCY.USD, extraDecimals = 0, errorText, onInputChange, onCurrencyButtonPress}: AmountFormProps, forwardedRef: ForwardedRef) { const styles = useThemeStyles(); const {toLocaleDigit, numberFormat} = useLocalize(); const textInput = useRef(null); - const decimals = CurrencyUtils.getCurrencyDecimals(currency); + const decimals = CurrencyUtils.getCurrencyDecimals(currency) + extraDecimals; const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]); const [shouldUpdateSelection, setShouldUpdateSelection] = useState(true); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index cbc9a3f72e11..0a220308f596 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -96,6 +96,7 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { InputComponent={AmountForm} inputID="rate" currency={props.policy?.outputCurrency ?? CONST.CURRENCY.USD} + extraDecimals={1} defaultValue={( (typeof props.workspaceRateAndUnit?.rate === 'string' ? parseFloat(props.workspaceRateAndUnit.rate) : defaultValue) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET ).toString()} From befbb4209913d3c345d7114eba6f6b64e51a09e5 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 5 Feb 2024 07:50:24 +0530 Subject: [PATCH 25/35] fixed minor bug --- src/libs/MoneyRequestUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index ff86070395b3..725e9c7fdc18 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -34,10 +34,10 @@ function addLeadingZero(amount: string): string { /** * Calculate the length of the amount with leading zeroes */ -function calculateAmountLength(amount: string): number { +function calculateAmountLength(amount: string, decimals: number): number { const leadingZeroes = amount.match(/^0+/); const leadingZeroesLength = leadingZeroes?.[0]?.length ?? 0; - const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * 100).toFixed(2)).toString(); + const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * (10 ** decimals)).toFixed(2)).toString(); if (/\D/.test(absAmount)) { return CONST.IOU.AMOUNT_MAX_LENGTH + 1; @@ -55,7 +55,7 @@ function validateAmount(amount: string, decimals: number): boolean { ? `^\\d+(,\\d*)*$` // Don't allow decimal point if decimals === 0 : `^\\d+(,\\d*)*(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point const decimalNumberRegex = new RegExp(regexString, 'i'); - return amount === '' || (decimalNumberRegex.test(amount) && calculateAmountLength(amount) <= CONST.IOU.AMOUNT_MAX_LENGTH); + return amount === '' || (decimalNumberRegex.test(amount) && calculateAmountLength(amount, decimals) <= CONST.IOU.AMOUNT_MAX_LENGTH); } /** From c7f62c90ce42a3027baef6b3123f62174511a721 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 5 Feb 2024 08:02:31 +0530 Subject: [PATCH 26/35] fixed bug with round off --- .../workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 0a220308f596..674818156a19 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -44,7 +44,7 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { const submit = (values: OnyxFormValuesFields) => { const rate = values.rate as string; - Policy.setRateForReimburseView((parseFloat(rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toString()); + Policy.setRateForReimburseView((parseFloat(rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET).toFixed(1)); Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; @@ -99,7 +99,7 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { extraDecimals={1} defaultValue={( (typeof props.workspaceRateAndUnit?.rate === 'string' ? parseFloat(props.workspaceRateAndUnit.rate) : defaultValue) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET - ).toString()} + ).toFixed(3)} /> )} From 54da1066be620e1390306037829cc706414d7dea Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 5 Feb 2024 08:05:54 +0530 Subject: [PATCH 27/35] fixed bug with initial route --- .../workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx index ba925ea968ee..4d7f21fe6f3c 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx @@ -105,6 +105,7 @@ function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} shouldSkipVBBACall + backButtonRoute={ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy?.id ?? '')} shouldShowLoading={false} shouldShowBackButton > From d63311d65f4f8816af2ac42fda3d2226a6eef90d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 5 Feb 2024 08:11:33 +0530 Subject: [PATCH 28/35] fix lint --- src/components/AmountForm.tsx | 5 ++++- src/libs/MoneyRequestUtils.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 9603ecaba46d..b5362e4964fa 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -46,7 +46,10 @@ const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; -function AmountForm({value: amount, currency = CONST.CURRENCY.USD, extraDecimals = 0, errorText, onInputChange, onCurrencyButtonPress}: AmountFormProps, forwardedRef: ForwardedRef) { +function AmountForm( + {value: amount, currency = CONST.CURRENCY.USD, extraDecimals = 0, errorText, onInputChange, onCurrencyButtonPress}: AmountFormProps, + forwardedRef: ForwardedRef, +) { const styles = useThemeStyles(); const {toLocaleDigit, numberFormat} = useLocalize(); diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index 725e9c7fdc18..da8a5b843ec1 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -37,7 +37,7 @@ function addLeadingZero(amount: string): string { function calculateAmountLength(amount: string, decimals: number): number { const leadingZeroes = amount.match(/^0+/); const leadingZeroesLength = leadingZeroes?.[0]?.length ?? 0; - const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * (10 ** decimals)).toFixed(2)).toString(); + const absAmount = parseFloat((Number(stripCommaFromAmount(amount)) * 10 ** decimals).toFixed(2)).toString(); if (/\D/.test(absAmount)) { return CONST.IOU.AMOUNT_MAX_LENGTH + 1; From 840e931a98fc7242e29867c0b31831a2a85ed476 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 8 Feb 2024 10:04:44 +0530 Subject: [PATCH 29/35] fix ts --- src/libs/CurrencyUtils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 585270a18f36..4098cbcd31fc 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -115,13 +115,8 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR * * @param amount – should be a float. * @param currency - IOU currency - * @param shouldFallbackToTbd - whether to return 'TBD' instead of a falsy value (e.g. 0.00) */ -function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRENCY.USD, shouldFallbackToTbd = false): string { - if (shouldFallbackToTbd && !amount) { - return Localize.translateLocal('common.tbd'); - } - +function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRENCY.USD): string { const convertedAmount = amount / 100.0; return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', From 659fa2300d91578d785bec51f85dee3cb6f64f11 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 8 Feb 2024 15:11:51 +0530 Subject: [PATCH 30/35] fix naigation bug --- src/pages/workspace/WorkspacePageWithSections.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 70198f38f18c..621541125809 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -84,7 +84,7 @@ function fetchData(skipVBBACal?: boolean) { } function WorkspacePageWithSections({ - backButtonRoute = '', + backButtonRoute, children = () => null, footer = null, guidesCallTaskID = '', From f3e6326e645735cac6a9fa150984de05b7a68a87 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 8 Feb 2024 15:55:32 +0530 Subject: [PATCH 31/35] Fix another navigation bug --- .../reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx index 4d7f21fe6f3c..1cafc20e5c3f 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx @@ -105,7 +105,7 @@ function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} shouldSkipVBBACall - backButtonRoute={ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy?.id ?? '')} + backButtonRoute="" shouldShowLoading={false} shouldShowBackButton > From 1d5a65368f62f6514d20344904a3617464707191 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Sun, 11 Feb 2024 13:25:17 +0530 Subject: [PATCH 32/35] Fix style layout of rate page --- src/components/Form/FormProvider.tsx | 8 +++++++- .../reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index ba0f823fdbad..a793dfbb9f33 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -1,7 +1,7 @@ import lodashIsEqual from 'lodash/isEqual'; import type {ForwardedRef, MutableRefObject, ReactNode} from 'react'; import React, {createRef, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import type {NativeSyntheticEvent, TextInputSubmitEditingEventData} from 'react-native'; +import type {NativeSyntheticEvent, StyleProp, TextInputSubmitEditingEventData, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -62,6 +62,12 @@ type FormProviderProps = FormProvider /** Should validate function be called when the value of the input is changed */ shouldValidateOnChange?: boolean; + + /** Submit button styles */ + submitButtonStyles?: StyleProp; + + /** Whether to apply flex to the submit button */ + submitFlexEnabled?: boolean; }; type FormRef = { diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 674818156a19..53906b05346f 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -87,10 +87,10 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { validate={validate} onSubmit={submit} enabledWhenOffline - style={[styles.flexGrow1, styles.mh5]} + style={[styles.flexGrow1]} shouldHideFixErrorsAlert - // @ts-expect-error TODO: fix this submitFlexEnabled={false} + submitButtonStyles={[styles.mh5, styles.mt0]} > Date: Mon, 12 Feb 2024 10:33:53 +0530 Subject: [PATCH 33/35] Fix header title --- src/languages/en.ts | 2 -- src/languages/es.ts | 2 -- .../workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 2 +- .../workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 57d5595e7acc..afd9de04a1b9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1621,9 +1621,7 @@ export default { trackDistance: 'Track distance', trackDistanceCopy: 'Set the per mile/km rate and choose a default unit to track.', trackDistanceRate: 'Rate', - trackDistanceRateTitle: 'Track distance: Rate', trackDistanceUnit: 'Unit', - trackDistanceUnitTitle: 'Track distance: Unit', trackDistanceChooseUnit: 'Choose a default unit to track.', kilometers: 'Kilometers', miles: 'Miles', diff --git a/src/languages/es.ts b/src/languages/es.ts index 7897f56ad6b5..2a0fa1d6dc35 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1644,9 +1644,7 @@ export default { trackDistance: 'Medir distancia', trackDistanceCopy: 'Configura la tarifa y unidad usadas para medir distancias.', trackDistanceRate: 'Tarifa', - trackDistanceRateTitle: 'Medir distancia: Tarifa', trackDistanceUnit: 'Unidad', - trackDistanceUnitTitle: 'Medir distancia: Unidad', trackDistanceChooseUnit: 'Elija una unidad predeterminada para rastrear.', kilometers: 'Kilómetros', miles: 'Millas', diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 53906b05346f..37723fe654c0 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -72,7 +72,7 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { return ( Date: Mon, 12 Feb 2024 22:59:50 +0530 Subject: [PATCH 34/35] Adding comments --- src/ONYXKEYS.ts | 7 ++++++- src/components/AmountForm.tsx | 6 +++--- src/components/Form/FormProvider.tsx | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1b0b0a1212dc..1586a2008cdd 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -63,7 +63,12 @@ const ONYXKEYS = { /** Contains all the info for Tasks */ TASK: 'task', - /** Contains all the info for Workspace Rate and Unit (editing) */ + /** + * Contains all the info for Workspace Rate and Unit while editing. + * + * Note: This is not under the COLLECTION key as we can edit rate and unit + * for one workspace only at a time. And we don't need to store + * rates and units for different workspaces at the same time. */ WORKSPACE_RATE_AND_UNIT: 'workspaceRateAndUnit', /** Contains a list of all currencies available to the user - user can diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index b5362e4964fa..4214d804af06 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -70,7 +70,7 @@ function AmountForm( /** * Event occurs when a user presses a mouse button over an DOM element. */ - const onMouseDown = (event: React.MouseEvent, ids: string[]) => { + const focusTextInput = (event: React.MouseEvent, ids: string[]) => { const relatedTargetId = (event.nativeEvent?.target as HTMLElement | null)?.id ?? ''; if (!ids.includes(relatedTargetId)) { return; @@ -183,7 +183,7 @@ function AmountForm( <> onMouseDown(event, [AMOUNT_VIEW_ID])} + onMouseDown={(event) => focusTextInput(event, [AMOUNT_VIEW_ID])} style={[styles.moneyRequestAmountContainer, styles.flex1, styles.flexRow, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]} > {canUseTouchScreen ? ( onMouseDown(event, [NUM_PAD_CONTAINER_VIEW_ID, NUM_PAD_VIEW_ID])} + onMouseDown={(event) => focusTextInput(event, [NUM_PAD_CONTAINER_VIEW_ID, NUM_PAD_VIEW_ID])} style={[styles.w100, styles.justifyContentEnd, styles.pageWrapper, styles.pt0]} id={NUM_PAD_CONTAINER_VIEW_ID} > diff --git a/src/components/Form/FormProvider.tsx b/src/components/Form/FormProvider.tsx index a793dfbb9f33..82a8d0870946 100644 --- a/src/components/Form/FormProvider.tsx +++ b/src/components/Form/FormProvider.tsx @@ -63,7 +63,7 @@ type FormProviderProps = FormProvider /** Should validate function be called when the value of the input is changed */ shouldValidateOnChange?: boolean; - /** Submit button styles */ + /** Styles that will be applied to the submit button only */ submitButtonStyles?: StyleProp; /** Whether to apply flex to the submit button */ From 6b91fe594df6f70a1d043449488a1cb1456b03d2 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 12 Feb 2024 23:09:45 +0530 Subject: [PATCH 35/35] fix lint --- src/ONYXKEYS.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1586a2008cdd..68e23fae2f9f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -63,9 +63,9 @@ const ONYXKEYS = { /** Contains all the info for Tasks */ TASK: 'task', - /** + /** * Contains all the info for Workspace Rate and Unit while editing. - * + * * Note: This is not under the COLLECTION key as we can edit rate and unit * for one workspace only at a time. And we don't need to store * rates and units for different workspaces at the same time. */