From c14fbf85cbf91608e7b7f4ed7b2a6426a3bc57ed Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 7 Mar 2024 15:14:11 +0100 Subject: [PATCH 01/13] feat: PolicyNewDistanceRatePage --- src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../CreatePolicyDistanceRateParams.ts | 9 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 1 + .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + src/libs/actions/Policy.ts | 51 +++++++++++ .../distanceRates/PolicyDistanceRatesPage.tsx | 4 +- .../PolicyNewDistanceRatePage.tsx | 86 +++++++++++++++++++ .../form/PolicyCreateDistanceRateForm.ts | 18 ++++ src/types/form/index.ts | 1 + 15 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/libs/API/parameters/CreatePolicyDistanceRateParams.ts create mode 100644 src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx create mode 100644 src/types/form/PolicyCreateDistanceRateForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index f6b5c635e4ae..ce2a2be262e9 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -331,6 +331,8 @@ const ONYXKEYS = { WORKSPACE_DESCRIPTION_FORM_DRAFT: 'workspaceDescriptionFormDraft', WORKSPACE_RATE_AND_UNIT_FORM: 'workspaceRateAndUnitForm', WORKSPACE_RATE_AND_UNIT_FORM_DRAFT: 'workspaceRateAndUnitFormDraft', + POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm', + POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft', CLOSE_ACCOUNT_FORM: 'closeAccount', CLOSE_ACCOUNT_FORM_DRAFT: 'closeAccountDraft', PROFILE_SETTINGS_FORM: 'profileSettingsForm', @@ -443,6 +445,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM]: FormTypes.PolicyCreateDistanceRateForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index fee69b9d785a..61dc44ac0432 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -554,6 +554,10 @@ const ROUTES = { route: 'workspace/:policyID/distance-rates', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const, }, + WORKSPACE_CREATE_DISTANCE_RATE: { + route: 'workspace/:policyID/distance-rates/new', + getRoute: (policyID: string) => `workspace/${policyID}/distance-rates/new` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index caedab241349..4d572a65e24f 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -226,6 +226,7 @@ const SCREENS = { CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', DISTANCE_RATES: 'Distance_Rates', + CREATE_DISTANCE_RATE: 'Create_Distance_Rate', }, EDIT_REQUEST: { diff --git a/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts new file mode 100644 index 000000000000..ea06408d4402 --- /dev/null +++ b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts @@ -0,0 +1,9 @@ +import type {Rate} from '@src/types/onyx/Policy'; + +type CreatePolicyDistanceRateParams = { + policyID: string; + customUnitID: string; + customUnitRate: Rate; +}; + +export default CreatePolicyDistanceRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 7c4d592fe48d..34cc473b5d50 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -156,3 +156,4 @@ export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './Se export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; +export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a4cec18ae646..dce2592eae08 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -156,6 +156,7 @@ const WRITE_COMMANDS = { CANCEL_PAYMENT: 'CancelPayment', ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount', SWITCH_TO_OLD_DOT: 'SwitchToOldDot', + CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', } as const; type WriteCommand = ValueOf; @@ -310,6 +311,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET]: Parameters.SetWorkspaceAutoReportingMonthlyOffsetParams; [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams; [WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams; + [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 545641957c9a..c6cf3d515336 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -251,6 +251,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/PolicyNewDistanceRatePage').default as React.ComponentType, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, [SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType, [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').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 7959999ee813..1d2cad900ff1 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -7,6 +7,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE], [SCREENS.WORKSPACE.WORKFLOWS]: [SCREENS.WORKSPACE.WORKFLOWS_APPROVER, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], + [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 927b5b509277..a99d3e9cb695 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -283,6 +283,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route, }, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { + path: ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.route, + }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index f9b70b4b7ac8..73debd9d3897 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -207,6 +207,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { policyID: string; }; + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: { + policyID: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index b94ae68b04ac..2f9005633b48 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -10,6 +10,7 @@ import type {ValueOf} from 'type-fest'; import * as API from '@libs/API'; import type { AddMembersToWorkspaceParams, + CreatePolicyDistanceRateParams, CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteMembersFromWorkspaceParams, @@ -2514,6 +2515,54 @@ function openPolicyDistanceRatesPage(policyID?: string) { API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params); } +function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + ...customUnitRate, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.createRateGenericFailureMessage'), + pendingAction: null, + }, + }, + }, + }, + }, + }, + ]; + + const params: CreatePolicyDistanceRateParams = { + policyID, + customUnitID, + customUnitRate, + }; + + API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, failureData}); +} + export { removeMembers, updateWorkspaceMembersRole, @@ -2565,4 +2614,6 @@ export { setWorkspaceRequiresCategory, clearCategoryErrors, openPolicyDistanceRatesPage, + generateCustomUnitID, + createPolicyDistanceRate, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 08cd3dffe709..8c81ff7765b5 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -19,6 +19,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; @@ -26,6 +27,7 @@ import {openPolicyDistanceRatesPage} from '@userActions/Policy'; import ButtonWithDropdownMenu from '@src/components/ButtonWithDropdownMenu'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type Policy from '@src/types/onyx/Policy'; import type {CustomUnit, Rate} from '@src/types/onyx/Policy'; @@ -91,7 +93,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) ); const addRate = () => { - // Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID)); + Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID)); }; const openSettings = () => { diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx new file mode 100644 index 000000000000..8e794e51674d --- /dev/null +++ b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx @@ -0,0 +1,86 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import AmountForm from '@components/AmountForm'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import {createPolicyDistanceRate, generateCustomUnitID} from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/PolicyCreateDistanceRateForm'; +import type {Rate} from '@src/types/onyx/Policy'; +import type Policy from '@src/types/onyx/Policy'; + +type PolicyNewDistanceRatePageOnyxProps = { + policy: OnyxEntry; +}; + +type PolicyDistanceRatePageProps = PolicyNewDistanceRatePageOnyxProps & StackScreenProps; + +function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currency = policy !== null ? policy?.outputCurrency : CONST.CURRENCY.USD; + const customUnits = policy?.customUnits ?? {}; + const customUnitID = customUnits[Object.keys(customUnits)[0]].customUnitID; + const customUnitRateID = generateCustomUnitID(); + + const submit = (values: FormOnyxValues) => { + const newRate: Rate = { + currency, + name: CONST.CUSTOM_UNITS.DEFAULT_RATE, + rate: Number(values.rate), + customUnitRateID, + }; + + createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); + Navigation.goBack(); + }; + + return ( + + + + + + + + + + + ); +} + +PolicyNewDistanceRatePage.displayName = 'CreateDistanceRatePage'; + +export default withOnyx({ + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, +})(PolicyNewDistanceRatePage); diff --git a/src/types/form/PolicyCreateDistanceRateForm.ts b/src/types/form/PolicyCreateDistanceRateForm.ts new file mode 100644 index 000000000000..11bb9f4aa83c --- /dev/null +++ b/src/types/form/PolicyCreateDistanceRateForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + RATE: 'rate', +} as const; + +type InputID = ValueOf; + +type PolicyCreateDistanceRateForm = Form< + InputID, + { + [INPUT_IDS.RATE]: string; + } +>; + +export type {PolicyCreateDistanceRateForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 1ff8d0df2031..bf58c78d0b90 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -37,4 +37,5 @@ export type {WorkspaceRateAndUnitForm} from './WorkspaceRateAndUnitForm'; export type {WorkspaceSettingsForm} from './WorkspaceSettingsForm'; export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; +export type {PolicyCreateDistanceRateForm} from './PolicyCreateDistanceRateForm'; export type {default as Form} from './Form'; From f7567c3e11d944d1686d439da5e4aaf89fc6e93d Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 8 Mar 2024 15:18:11 +0100 Subject: [PATCH 02/13] feat: adding delete --- src/languages/en.ts | 3 + .../CreatePolicyDistanceRateParams.ts | 4 +- .../DeletePolicyDistanceRatesParams.ts | 6 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Policy.ts | 127 +++++++++++++++++- .../distanceRates/PolicyDistanceRatesPage.tsx | 44 +++--- .../PolicyNewDistanceRatePage.tsx | 11 +- 8 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index d7d8e10eb64f..a74a22bad563 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1926,6 +1926,9 @@ export default { status: 'Status', enabled: 'Enabled', disabled: 'Disabled', + errors: { + createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', + }, }, editor: { descriptionInputLabel: 'Description', diff --git a/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts index ea06408d4402..82b796a23960 100644 --- a/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts +++ b/src/libs/API/parameters/CreatePolicyDistanceRateParams.ts @@ -1,9 +1,7 @@ -import type {Rate} from '@src/types/onyx/Policy'; - type CreatePolicyDistanceRateParams = { policyID: string; customUnitID: string; - customUnitRate: Rate; + customUnitRate: string; }; export default CreatePolicyDistanceRateParams; diff --git a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts new file mode 100644 index 000000000000..801775631ef2 --- /dev/null +++ b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts @@ -0,0 +1,6 @@ +type DeletePolicyDistanceRatesParams = { + policyID: string; + customUnitRateIDs: string[]; +}; + +export default DeletePolicyDistanceRatesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 85f3d9d87f57..1d1be3ea2e28 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -161,3 +161,4 @@ export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest'; export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; +export type {default as DeletePolicyDistanceRatesParams} from './DeletePolicyDistanceRatesParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 51cf2721878b..a8d575bf9767 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -161,6 +161,7 @@ const WRITE_COMMANDS = { ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', + DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates', } as const; type WriteCommand = ValueOf; @@ -320,6 +321,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; + [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 8c3e2af51fd8..33480cb42f4a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -14,6 +14,7 @@ import type { CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteMembersFromWorkspaceParams, + DeletePolicyDistanceRatesParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, OpenDraftWorkspaceRequestParams, @@ -2719,7 +2720,11 @@ function openPolicyDistanceRatesPage(policyID?: string) { API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params); } -function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) { +function createPolicyDistanceRate(customUnitRate: Rate, customUnitID: string, policyID?: string) { + if (!policyID) { + return; + } + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2739,6 +2744,25 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom }, ]; + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + rates: { + [customUnitRate.customUnitRateID ?? '']: { + errors: null, + pendingAction: null, + }, + }, + }, + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -2749,7 +2773,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom rates: { [customUnitRate.customUnitRateID ?? '']: { errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.createRateGenericFailureMessage'), - pendingAction: null, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, }, }, @@ -2761,10 +2785,103 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom const params: CreatePolicyDistanceRateParams = { policyID, customUnitID, - customUnitRate, + customUnitRate: JSON.stringify(customUnitRate), + }; + + API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, successData, failureData}); +} + +function clearCreateDistanceRateError(policyID: string, currentRates: Record, customUnitID?: string, customUnitRateIDToClear?: string) { + if (!policyID || !customUnitID || !customUnitRateIDToClear) { + return; + } + + const updatedRates = {...currentRates}; + delete updatedRates[customUnitRateIDToClear]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + customUnits: { + [customUnitID]: { + rates: updatedRates, + }, + }, + }); +} + +function deletePolicyDistanceRates(policyID: string, rateIDsToDelete: string[], customUnit?: CustomUnit) { + if (!policyID || !rateIDsToDelete || !customUnit) { + return; + } + + const currentRates = customUnit.rates; + const optimisticRates: Record = {}; + const successRates: Record = {}; + + Object.keys(customUnit.rates).forEach((rateID) => { + if (rateIDsToDelete.includes(rateID)) { + optimisticRates[rateID] = { + ...customUnit.rates[rateID], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }; + successRates[rateID] = { + ...customUnit.rates[rateID], + pendingAction: null, + }; + } else { + optimisticRates[rateID] = customUnit.rates[rateID]; + successRates[rateID] = customUnit.rates[rateID]; + } + }); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: { + rates: optimisticRates, + }, + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: { + rates: successRates, + }, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: { + rates: currentRates, + }, + errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.deleteRateGenericFailureMessage'), + }, + }, + }, + ]; + + const params: DeletePolicyDistanceRatesParams = { + policyID, + customUnitRateIDs: rateIDsToDelete, }; - API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, failureData}); + API.write(WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES, params, {optimisticData, successData, failureData}); } export { @@ -2824,4 +2941,6 @@ export { openPolicyDistanceRatesPage, generateCustomUnitID, createPolicyDistanceRate, + clearCreateDistanceRateError, + deletePolicyDistanceRates, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index dfa6817f235d..8f2b01ff69f7 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -13,6 +13,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import TableListItem from '@components/SelectionList/TableListItem'; +import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -33,13 +34,7 @@ import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {CustomUnit, Rate} from '@src/types/onyx/Policy'; -type RateForList = { - value: string; - text: string; - keyForList: string; - isSelected: boolean; - rightElement: React.ReactNode; -}; +type RateForList = ListItem & {value: string}; type PolicyDistanceRatesPageOnyxProps = { /** Policy details */ @@ -56,6 +51,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const [selectedDistanceRates, setSelectedDistanceRates] = useState([]); const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const dropdownButtonRef = useRef(null); + const policyID = route.params.policyID; const customUnit: CustomUnit | undefined = useMemo( () => (policy?.customUnits !== undefined ? policy?.customUnits[Object.keys(policy?.customUnits)[0]] : undefined), @@ -64,9 +60,19 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const customUnitRates: Record = useMemo(() => customUnit?.rates ?? {}, [customUnit]); function fetchDistanceRates() { - Policy.openPolicyDistanceRatesPage(route.params.policyID); + Policy.openPolicyDistanceRatesPage(policyID); } + const dismissError = useCallback( + (item: RateForList) => { + if (item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { + return; + } + Policy.clearCreateDistanceRateError(policyID, customUnitRates, customUnit?.customUnitID, item.value); + }, + [customUnit?.customUnitID, customUnitRates, policyID], + ); + const {isOffline} = useNetwork({onReconnect: fetchDistanceRates}); useEffect(() => { @@ -83,6 +89,8 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) )}`, keyForList: value.customUnitRateID ?? '', isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined, + pendingAction: value.pendingAction, + errors: value.errors ?? undefined, rightElement: ( @@ -101,15 +109,15 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) ); const addRate = () => { - Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID)); + Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(policyID)); }; const openSettings = () => { - // Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(route.params.policyID)); + // Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(policyID)); }; const editRate = () => { - // Navigation.navigate(ROUTES.WORKSPACE_EDIT_DISTANCE_RATE.getRoute(route.params.policyID, rateID)); + // Navigation.navigate(ROUTES.WORKSPACE_EDIT_DISTANCE_RATE.getRoute(policyID, rateID)); }; const disableRates = () => { @@ -127,7 +135,12 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const deleteRates = () => { if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { - // run deleteWorkspaceDistanceRates for all selected rows + Policy.deletePolicyDistanceRates( + policyID, + selectedDistanceRates.map((rate) => rate.customUnitRateID ?? ''), + customUnit, + ); + setSelectedDistanceRates([]); return; } @@ -232,8 +245,8 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) ); return ( - - + + - + ({ policy: { From d0d8d2c58791f0db6bd7233f846359375d00bd28 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 12:22:19 +0100 Subject: [PATCH 03/13] feat: add --- src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts | 1 + src/libs/actions/Policy.ts | 1 + src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts index 801775631ef2..d9c31930897e 100644 --- a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts +++ b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts @@ -1,5 +1,6 @@ type DeletePolicyDistanceRatesParams = { policyID: string; + customUnitID: string; customUnitRateIDs: string[]; }; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 42b98bf0742d..a390df8d2332 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2929,6 +2929,7 @@ function deletePolicyDistanceRates(policyID: string, rateIDsToDelete: string[], const params: DeletePolicyDistanceRatesParams = { policyID, + customUnitID: customUnit.customUnitID, customUnitRateIDs: rateIDsToDelete, }; diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx index 8debf6a69cdd..f7ebdf27cb73 100644 --- a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx @@ -45,7 +45,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) enabled: true, }; - createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); + createPolicyDistanceRate(newRate, customUnitID, route.params.policyID); Navigation.goBack(); }; From d27b9fed4059e68cb9808204fccd75d6a70ba0e2 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:21:14 +0100 Subject: [PATCH 04/13] fix: ts error --- src/languages/en.ts | 1 + src/languages/es.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index be2d0dc569fd..8beffa9e8545 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1928,6 +1928,7 @@ export default { enabled: 'Enabled', disabled: 'Disabled', errors: { + deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 9e2418d89233..97cd315dc072 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1952,6 +1952,10 @@ export default { status: 'Estado', enabled: 'Activada', disabled: 'Desactivada', + errors: { + deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', + createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', + }, }, editor: { nameInputLabel: 'Nombre', From cfa91452f167636ea4ea6952a22975551e0e2864 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:32:40 +0100 Subject: [PATCH 05/13] fix: scrap delete from this PR --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - .../DeletePolicyDistanceRatesParams.ts | 7 -- src/libs/API/parameters/index.ts | 1 - src/libs/API/types.ts | 2 - src/libs/actions/Policy.ts | 79 ------------------- .../distanceRates/PolicyDistanceRatesPage.tsx | 8 +- 7 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 8beffa9e8545..be2d0dc569fd 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1928,7 +1928,6 @@ export default { enabled: 'Enabled', disabled: 'Disabled', errors: { - deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 97cd315dc072..b85071197298 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1953,7 +1953,6 @@ export default { enabled: 'Activada', disabled: 'Desactivada', errors: { - deleteRateGenericFailureMessage: 'An error occurred while deleting the distance rate, please try again.', createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', }, }, diff --git a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts deleted file mode 100644 index d9c31930897e..000000000000 --- a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts +++ /dev/null @@ -1,7 +0,0 @@ -type DeletePolicyDistanceRatesParams = { - policyID: string; - customUnitID: string; - customUnitRateIDs: string[]; -}; - -export default DeletePolicyDistanceRatesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 1d1be3ea2e28..85f3d9d87f57 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -161,4 +161,3 @@ export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest'; export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink'; export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; -export type {default as DeletePolicyDistanceRatesParams} from './DeletePolicyDistanceRatesParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a8d575bf9767..51cf2721878b 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -161,7 +161,6 @@ const WRITE_COMMANDS = { ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', - DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates', } as const; type WriteCommand = ValueOf; @@ -321,7 +320,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE]: Parameters.CreatePolicyDistanceRateParams; - [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index a390df8d2332..e6fcbe9e1ba9 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -14,7 +14,6 @@ import type { CreateWorkspaceFromIOUPaymentParams, CreateWorkspaceParams, DeleteMembersFromWorkspaceParams, - DeletePolicyDistanceRatesParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, OpenDraftWorkspaceRequestParams, @@ -2859,83 +2858,6 @@ function clearCreateDistanceRateError(policyID: string, currentRates: Record = {}; - const successRates: Record = {}; - - Object.keys(customUnit.rates).forEach((rateID) => { - if (rateIDsToDelete.includes(rateID)) { - optimisticRates[rateID] = { - ...customUnit.rates[rateID], - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - }; - successRates[rateID] = { - ...customUnit.rates[rateID], - pendingAction: null, - }; - } else { - optimisticRates[rateID] = customUnit.rates[rateID]; - successRates[rateID] = customUnit.rates[rateID]; - } - }); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - customUnits: { - [customUnit.customUnitID]: { - rates: optimisticRates, - }, - }, - }, - }, - ]; - - const successData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - customUnits: { - [customUnit.customUnitID]: { - rates: successRates, - }, - }, - }, - }, - ]; - - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - customUnits: { - [customUnit.customUnitID]: { - rates: currentRates, - }, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.deleteRateGenericFailureMessage'), - }, - }, - }, - ]; - - const params: DeletePolicyDistanceRatesParams = { - policyID, - customUnitID: customUnit.customUnitID, - customUnitRateIDs: rateIDsToDelete, - }; - - API.write(WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES, params, {optimisticData, successData, failureData}); -} - export { removeMembers, updateWorkspaceMembersRole, @@ -2994,5 +2916,4 @@ export { generateCustomUnitID, createPolicyDistanceRate, clearCreateDistanceRateError, - deletePolicyDistanceRates, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 8f2b01ff69f7..a2838e9fd04c 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -135,13 +135,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const deleteRates = () => { if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { - Policy.deletePolicyDistanceRates( - policyID, - selectedDistanceRates.map((rate) => rate.customUnitRateID ?? ''), - customUnit, - ); - setSelectedDistanceRates([]); - return; + // run deleteWorkspaceDistanceRates for all selected rows } setIsWarningModalVisible(true); From 2b1baf71e918bb6017578c8d627ac7e01e7ec2c7 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:34:05 +0100 Subject: [PATCH 06/13] fix: one line --- src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index a2838e9fd04c..b9e1ca47b8a8 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -136,6 +136,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const deleteRates = () => { if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { // run deleteWorkspaceDistanceRates for all selected rows + return; } setIsWarningModalVisible(true); From 1b53b39dfd7f390eddf51ad184f3c2f5776b9ed2 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 11 Mar 2024 13:51:34 +0100 Subject: [PATCH 07/13] feat: add validation --- .../PolicyNewDistanceRatePage.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx index f7ebdf27cb73..b0745cb52055 100644 --- a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx @@ -4,12 +4,16 @@ import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormOnyxValues} from '@components/Form/types'; +import InputWrapperWithRef from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; +import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; +import * as NumberUtils from '@libs/NumberUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; @@ -30,12 +34,27 @@ type PolicyDistanceRatePageProps = PolicyNewDistanceRatePageOnyxProps & StackScr function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = useLocalize(); const currency = policy !== null ? policy?.outputCurrency : CONST.CURRENCY.USD; const customUnits = policy?.customUnits ?? {}; const customUnitID = customUnits[Object.keys(customUnits)[0]].customUnitID; const customUnitRateID = generateCustomUnitID(); + const validate = (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + const rate = values.rate; + const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); + const decimalSeparator = toLocaleDigit('.'); + // Allow one more decimal place for accuracy + const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(currency) + 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 submit = (values: FormOnyxValues) => { const newRate: Rate = { currency, @@ -62,13 +81,14 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) formID={ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM} submitButtonText={translate('common.save')} onSubmit={submit} + validate={validate} enabledWhenOffline style={[styles.flexGrow1]} shouldHideFixErrorsAlert submitFlexEnabled={false} submitButtonStyles={[styles.mh5, styles.mt0]} > - Date: Wed, 13 Mar 2024 11:01:31 +0100 Subject: [PATCH 08/13] fix: cr fixes --- src/languages/en.ts | 3 --- src/languages/es.ts | 3 --- .../AppNavigator/ModalStackNavigators.tsx | 2 +- src/libs/actions/Policy.ts | 10 +++------- ...atePage.tsx => CreateDistanceRatePage.tsx} | 19 ++++++++++--------- 5 files changed, 14 insertions(+), 23 deletions(-) rename src/pages/workspace/distanceRates/{PolicyNewDistanceRatePage.tsx => CreateDistanceRatePage.tsx} (88%) diff --git a/src/languages/en.ts b/src/languages/en.ts index 8a402f7f7f42..7e442eee2236 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1983,9 +1983,6 @@ export default { status: 'Status', enabled: 'Enabled', disabled: 'Disabled', - errors: { - createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', - }, }, editor: { descriptionInputLabel: 'Description', diff --git a/src/languages/es.ts b/src/languages/es.ts index e0e40333947d..267581f043ee 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2008,9 +2008,6 @@ export default { status: 'Estado', enabled: 'Activada', disabled: 'Desactivada', - errors: { - createRateGenericFailureMessage: 'An error occurred while creating the distance rate, please try again.', - }, }, editor: { nameInputLabel: 'Nombre', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index a78be0ab5267..148cd6e6cbc9 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -262,7 +262,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/members/WorkspaceMemberDetailsPage').default as React.ComponentType, [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType, - [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/PolicyNewDistanceRatePage').default as React.ComponentType, + [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('@pages/workspace/distanceRates/CreateDistanceRatePage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_CREATE]: () => require('../../../pages/workspace/tags/WorkspaceCreateTagPage').default as React.ComponentType, diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 7c6bfe2148bb..b4425ae5803a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3449,11 +3449,7 @@ function openPolicyDistanceRatesPage(policyID?: string) { API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params); } -function createPolicyDistanceRate(customUnitRate: Rate, customUnitID: string, policyID?: string) { - if (!policyID) { - return; - } - +function createPolicyDistanceRate(policyID: string, customUnitID: string, customUnitRate: Rate) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -3501,8 +3497,8 @@ function createPolicyDistanceRate(customUnitRate: Rate, customUnitID: string, po [customUnitID]: { rates: { [customUnitRate.customUnitRateID ?? '']: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.distanceRates.errors.createRateGenericFailureMessage'), - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), + pendingAction: null, }, }, }, diff --git a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx similarity index 88% rename from src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx rename to src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index b0745cb52055..627fba15e9e3 100644 --- a/src/pages/workspace/distanceRates/PolicyNewDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -26,18 +26,18 @@ import INPUT_IDS from '@src/types/form/PolicyCreateDistanceRateForm'; import type {Rate} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; -type PolicyNewDistanceRatePageOnyxProps = { +type CreateDistanceRatePageOnyxProps = { policy: OnyxEntry; }; -type PolicyDistanceRatePageProps = PolicyNewDistanceRatePageOnyxProps & StackScreenProps; +type CreateDistanceRatePageProps = CreateDistanceRatePageOnyxProps & StackScreenProps; -function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) { +function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const currency = policy !== null ? policy?.outputCurrency : CONST.CURRENCY.USD; const customUnits = policy?.customUnits ?? {}; - const customUnitID = customUnits[Object.keys(customUnits)[0]].customUnitID; + const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; const customUnitRateID = generateCustomUnitID(); const validate = (values: FormOnyxValues): FormInputErrors => { @@ -45,6 +45,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) const rate = values.rate; const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); const decimalSeparator = toLocaleDigit('.'); + // Allow one more decimal place for accuracy const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(currency) + 1}})?$`, 'i'); if (!rateValueRegex.test(parsedRate) || parsedRate === '') { @@ -64,7 +65,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) enabled: true, }; - createPolicyDistanceRate(newRate, customUnitID, route.params.policyID); + createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); Navigation.goBack(); }; @@ -74,7 +75,7 @@ function PolicyNewDistanceRatePage({policy, route}: PolicyDistanceRatePageProps) ({ +export default withOnyx({ policy: { key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, }, -})(PolicyNewDistanceRatePage); +})(CreateDistanceRatePage); From ba3f3b36fa586f9231eda92b5878926a9e24efae Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Wed, 13 Mar 2024 14:43:09 +0100 Subject: [PATCH 09/13] fix: error handling --- src/libs/actions/Policy.ts | 9 ++------- .../distanceRates/PolicyDistanceRatesPage.tsx | 11 ++++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index b4425ae5803a..378dc3f5078a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3498,7 +3498,6 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom rates: { [customUnitRate.customUnitRateID ?? '']: { errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), - pendingAction: null, }, }, }, @@ -3516,11 +3515,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, successData, failureData}); } -function clearCreateDistanceRateError(policyID: string, currentRates: Record, customUnitID?: string, customUnitRateIDToClear?: string) { - if (!policyID || !customUnitID || !customUnitRateIDToClear) { - return; - } - +function clearCreateDistanceRateItemAndError(policyID: string, currentRates: Record, customUnitID: string, customUnitRateIDToClear: string) { const updatedRates = {...currentRates}; delete updatedRates[customUnitRateIDToClear]; @@ -3604,7 +3599,7 @@ export { openPolicyDistanceRatesPage, generateCustomUnitID, createPolicyDistanceRate, - clearCreateDistanceRateError, + clearCreateDistanceRateItemAndError, createPolicyTag, clearWorkspaceReimbursementErrors, }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 404748156c86..56da635d2725 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -65,10 +65,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const dismissError = useCallback( (item: RateForList) => { - if (item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { - return; - } - Policy.clearCreateDistanceRateError(policyID, customUnitRates, customUnit?.customUnitID, item.value); + Policy.clearCreateDistanceRateItemAndError(policyID, customUnitRates, customUnit?.customUnitID ?? '', item.value); }, [customUnit?.customUnitID, customUnitRates, policyID], ); @@ -269,13 +266,13 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) {Object.values(customUnitRates).length > 0 && ( From 0fd52588a50be8adfa7493293db4eaedf8c06d76 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 14 Mar 2024 09:50:52 +0100 Subject: [PATCH 10/13] fix: cr fixes --- src/libs/actions/Policy.ts | 12 +++++------- .../distanceRates/CreateDistanceRatePage.tsx | 19 ++++++++++++++++++- .../distanceRates/PolicyDistanceRatesPage.tsx | 4 ++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index dba57a3207ba..e5221eea5f0a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3547,7 +3547,6 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom [customUnitID]: { rates: { [customUnitRate.customUnitRateID ?? '']: { - errors: null, pendingAction: null, }, }, @@ -3576,7 +3575,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom ]; const params: CreatePolicyDistanceRateParams = { - policyID, + policyID: `${policyID}1`, customUnitID, customUnitRate: JSON.stringify(customUnitRate), }; @@ -3584,14 +3583,13 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom API.write(WRITE_COMMANDS.CREATE_POLICY_DISTANCE_RATE, params, {optimisticData, successData, failureData}); } -function clearCreateDistanceRateItemAndError(policyID: string, currentRates: Record, customUnitID: string, customUnitRateIDToClear: string) { - const updatedRates = {...currentRates}; - delete updatedRates[customUnitRateIDToClear]; - +function clearCreateDistanceRateItemAndError(policyID: string, customUnitID: string, customUnitRateIDToClear: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { customUnits: { [customUnitID]: { - rates: updatedRates, + rates: { + [customUnitRateIDToClear]: null, + }, }, }, }); diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index 627fba15e9e3..67fb46ec42ad 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -1,5 +1,6 @@ +import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useCallback, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; @@ -7,6 +8,7 @@ import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -39,6 +41,20 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { const customUnits = policy?.customUnits ?? {}; const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; const customUnitRateID = generateCustomUnitID(); + const textInputRef = useRef(null); + const focusTimeoutRef = useRef(null); + + useFocusEffect( + useCallback(() => { + focusTimeoutRef.current = setTimeout(() => textInputRef.current?.focus(), CONST.ANIMATED_TRANSITION); + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, []), + ); const validate = (values: FormOnyxValues): FormInputErrors => { const errors: FormInputErrors = {}; @@ -95,6 +111,7 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { extraDecimals={1} isCurrencyPressable={false} currency={currency} + ref={(e) => (textInputRef.current = e)} /> diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 56da635d2725..c15cdd6006a4 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -65,9 +65,9 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const dismissError = useCallback( (item: RateForList) => { - Policy.clearCreateDistanceRateItemAndError(policyID, customUnitRates, customUnit?.customUnitID ?? '', item.value); + Policy.clearCreateDistanceRateItemAndError(policyID, customUnit?.customUnitID ?? '', item.value); }, - [customUnit?.customUnitID, customUnitRates, policyID], + [customUnit?.customUnitID, policyID], ); const {isOffline} = useNetwork({onReconnect: fetchDistanceRates}); From d04d2a367e37b2c6346af624969b783d274a168b Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 15 Mar 2024 08:43:56 +0100 Subject: [PATCH 11/13] fix: last cr fixes --- src/libs/actions/Policy.ts | 2 +- .../distanceRates/CreateDistanceRatePage.tsx | 22 ++++--------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index e5221eea5f0a..b714e78dc8c8 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -3575,7 +3575,7 @@ function createPolicyDistanceRate(policyID: string, customUnitID: string, custom ]; const params: CreatePolicyDistanceRateParams = { - policyID: `${policyID}1`, + policyID, customUnitID, customUnitRate: JSON.stringify(customUnitRate), }; diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index 67fb46ec42ad..df03182e1f56 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -1,6 +1,5 @@ -import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useRef} from 'react'; +import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; @@ -8,8 +7,8 @@ import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; @@ -41,20 +40,7 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { const customUnits = policy?.customUnits ?? {}; const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; const customUnitRateID = generateCustomUnitID(); - const textInputRef = useRef(null); - const focusTimeoutRef = useRef(null); - - useFocusEffect( - useCallback(() => { - focusTimeoutRef.current = setTimeout(() => textInputRef.current?.focus(), CONST.ANIMATED_TRANSITION); - return () => { - if (!focusTimeoutRef.current) { - return; - } - clearTimeout(focusTimeoutRef.current); - }; - }, []), - ); + const {inputCallbackRef} = useAutoFocusInput(); const validate = (values: FormOnyxValues): FormInputErrors => { const errors: FormInputErrors = {}; @@ -111,7 +97,7 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { extraDecimals={1} isCurrencyPressable={false} currency={currency} - ref={(e) => (textInputRef.current = e)} + ref={inputCallbackRef} /> From 78bd3501a1c384784feb374fce063574342f8ade Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 15 Mar 2024 14:39:13 +0100 Subject: [PATCH 12/13] fix: move validate to util --- src/libs/PolicyDistanceRatesUtils.ts | 26 ++++++++++++++++ .../distanceRates/CreateDistanceRatePage.tsx | 30 +++++-------------- .../WorkspaceRateAndUnitPage/RatePage.tsx | 29 +++++------------- 3 files changed, 42 insertions(+), 43 deletions(-) create mode 100644 src/libs/PolicyDistanceRatesUtils.ts diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts new file mode 100644 index 000000000000..b55e333ee292 --- /dev/null +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -0,0 +1,26 @@ +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import type ONYXKEYS from '@src/ONYXKEYS'; +import * as CurrencyUtils from './CurrencyUtils'; +import getPermittedDecimalSeparator from './getPermittedDecimalSeparator'; +import * as MoneyRequestUtils from './MoneyRequestUtils'; +import * as NumberUtils from './NumberUtils'; + +type RateValueForm = typeof ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM | typeof ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM; + +function validateRateValue(values: FormOnyxValues, currency: string, toLocaleDigit: (arg: string) => string): FormInputErrors { + const errors: FormInputErrors = {}; + const rate = values.rate; + const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); + const decimalSeparator = toLocaleDigit('.'); + + // Allow one more decimal place for accuracy + const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(currency) + 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; +} + +export default validateRateValue; diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index df03182e1f56..d6f9ea29ac83 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -1,20 +1,17 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapperWithRef from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import type {FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; -import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; -import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; -import * as NumberUtils from '@libs/NumberUtils'; +import validateRateValue from '@libs/PolicyDistanceRatesUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; @@ -36,27 +33,16 @@ type CreateDistanceRatePageProps = CreateDistanceRatePageOnyxProps & StackScreen function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); - const currency = policy !== null ? policy?.outputCurrency : CONST.CURRENCY.USD; + const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD; const customUnits = policy?.customUnits ?? {}; const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; const customUnitRateID = generateCustomUnitID(); const {inputCallbackRef} = useAutoFocusInput(); - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors: FormInputErrors = {}; - const rate = values.rate; - const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); - const decimalSeparator = toLocaleDigit('.'); - - // Allow one more decimal place for accuracy - const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(currency) + 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 validate = useCallback( + (values: FormOnyxValues) => validateRateValue(values, currency, toLocaleDigit), + [currency, toLocaleDigit], + ); const submit = (values: FormOnyxValues) => { const newRate: Rate = { diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 65be798f9520..d83f1b1d77a7 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -1,18 +1,15 @@ -import React, {useEffect, useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {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 {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import type {FormOnyxValues} 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 * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as NumberUtils from '@libs/NumberUtils'; +import validateRateValue from '@libs/PolicyDistanceRatesUtils'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; @@ -34,6 +31,7 @@ type WorkspaceRatePageProps = WorkspaceRatePageBaseProps & WorkspaceRateAndUnitO function WorkspaceRatePage(props: WorkspaceRatePageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); + const outputCurrency = props.policy?.outputCurrency ?? CONST.CURRENCY.USD; useEffect(() => { if (props.workspaceRateAndUnit?.policyID === props.policy?.id) { @@ -49,21 +47,10 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')); }; - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors: FormInputErrors = {}; - const rate = values.rate; - const parsedRate = MoneyRequestUtils.replaceAllDigits(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.rate = 'workspace.reimburse.invalidRateError'; - } else if (NumberUtils.parseFloatAnyLocale(parsedRate) <= 0) { - errors.rate = 'workspace.reimburse.lowRateError'; - } - return errors; - }; + const validate = useCallback( + (values: FormOnyxValues) => validateRateValue(values, outputCurrency, toLocaleDigit), + [outputCurrency, toLocaleDigit], + ); const defaultValue = useMemo(() => { const defaultDistanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); From e096ce3a13cf4750be5ecbde24f30e09c9505be1 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 15 Mar 2024 14:42:40 +0100 Subject: [PATCH 13/13] fix: remove extra const --- src/libs/PolicyDistanceRatesUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index b55e333ee292..8f1438b1d092 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -9,8 +9,7 @@ type RateValueForm = typeof ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM | typeof function validateRateValue(values: FormOnyxValues, currency: string, toLocaleDigit: (arg: string) => string): FormInputErrors { const errors: FormInputErrors = {}; - const rate = values.rate; - const parsedRate = MoneyRequestUtils.replaceAllDigits(rate, toLocaleDigit); + const parsedRate = MoneyRequestUtils.replaceAllDigits(values.rate, toLocaleDigit); const decimalSeparator = toLocaleDigit('.'); // Allow one more decimal place for accuracy