diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d74e691fe10e..d3fab1b9fcde 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -353,6 +353,8 @@ const ONYXKEYS = { WORKSPACE_TAX_CUSTOM_NAME_DRAFT: 'workspaceTaxCustomNameDraft', POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm', POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft', + POLICY_DISTANCE_RATE_EDIT_FORM: 'policyDistanceRateEditForm', + POLICY_DISTANCE_RATE_EDIT_FORM_DRAFT: 'policyDistanceRateEditFormDraft', CLOSE_ACCOUNT_FORM: 'closeAccount', CLOSE_ACCOUNT_FORM_DRAFT: 'closeAccountDraft', PROFILE_SETTINGS_FORM: 'profileSettingsForm', @@ -482,6 +484,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm; [ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM]: FormTypes.WorkspaceNewTaxForm; [ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM]: FormTypes.PolicyCreateDistanceRateForm; + [ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_EDIT_FORM]: FormTypes.PolicyDistanceRateEditForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; }; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1f802c5036e3..af34b8b7e9ea 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -637,8 +637,16 @@ const ROUTES = { getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates/new` as const, }, WORKSPACE_DISTANCE_RATES_SETTINGS: { - route: 'settings/workspace/:policyID/distance-rates/settings', - getRoute: (policyID: string) => `settings/workspace/${policyID}/distance-rates/settings` as const, + route: 'settings/workspaces/:policyID/distance-rates/settings', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates/settings` as const, + }, + WORKSPACE_DISTANCE_RATE_DETAILS: { + route: 'settings/workspaces/:policyID/distance-rates/:rateID', + getRoute: (policyID: string, rateID: string) => `settings/workspaces/${policyID}/distance-rates/${rateID}` as const, + }, + WORKSPACE_DISTANCE_RATE_EDIT: { + route: 'settings/workspaces/:policyID/distance-rates/:rateID/edit', + getRoute: (policyID: string, rateID: string) => `settings/workspaces/${policyID}/distance-rates/${rateID}/edit` as const, }, // Referral program promotion REFERRAL_DETAILS_MODAL: { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 4d4e9ea327c6..9c33c7e63d7d 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -246,6 +246,8 @@ const SCREENS = { DISTANCE_RATES: 'Distance_Rates', CREATE_DISTANCE_RATE: 'Create_Distance_Rate', DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings', + DISTANCE_RATE_DETAILS: 'Distance_Rate_Details', + DISTANCE_RATE_EDIT: 'Distance_Rate_Edit', }, EDIT_REQUEST: { diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index fac78ee786a0..e691a5bdb191 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -107,6 +107,9 @@ type ListItem = { /** Whether to wrap long text up to 2 lines */ isMultilineSupported?: boolean; + + /** The search value from the selection list */ + searchText?: string | null; }; type ListItemProps = CommonListItemProps & { diff --git a/src/languages/en.ts b/src/languages/en.ts index 29618b083bd5..c2ab2a78569e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -20,6 +20,7 @@ import type { DeleteActionParams, DeleteConfirmationParams, DidSplitAmountMessageParams, + DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnterMagicCodeParams, @@ -2036,17 +2037,17 @@ export default { centrallyManage: 'Centrally manage rates, choose to track in miles or kilometers, and set a default category.', rate: 'Rate', addRate: 'Add rate', - deleteRate: 'Delete rate', - deleteRates: 'Delete rates', + deleteRates: ({count}: DistanceRateOperationsParams) => `Delete ${Str.pluralize('rate', 'rates', count)}`, + enableRates: ({count}: DistanceRateOperationsParams) => `Enable ${Str.pluralize('rate', 'rates', count)}`, + disableRates: ({count}: DistanceRateOperationsParams) => `Disable ${Str.pluralize('rate', 'rates', count)}`, enableRate: 'Enable rate', - disableRate: 'Disable rate', - disableRates: 'Disable rates', - enableRates: 'Enable rates', status: 'Status', enabled: 'Enabled', disabled: 'Disabled', unit: 'Unit', defaultCategory: 'Default category', + deleteDistanceRate: 'Delete distance rate', + areYouSureDelete: ({count}: DistanceRateOperationsParams) => `Are you sure you want to delete ${Str.pluralize('this rate', 'these rates', count)}?`, }, editor: { descriptionInputLabel: 'Description', diff --git a/src/languages/es.ts b/src/languages/es.ts index 874921ee911a..64ab09b8d9e0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -18,6 +18,7 @@ import type { DeleteActionParams, DeleteConfirmationParams, DidSplitAmountMessageParams, + DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnglishTranslation, @@ -2064,17 +2065,17 @@ export default { centrallyManage: 'Gestiona centralizadamente las tasas, elige si contabilizar en millas o kilómetros, y define una categoría por defecto', rate: 'Tasa', addRate: 'Agregar tasa', - deleteRate: 'Eliminar tasa', - deleteRates: 'Eliminar tasas', + deleteRates: ({count}: DistanceRateOperationsParams) => `Eliminar ${Str.pluralize('tasa', 'tasas', count)}`, + enableRates: ({count}: DistanceRateOperationsParams) => `Activar ${Str.pluralize('tasa', 'tasas', count)}`, + disableRates: ({count}: DistanceRateOperationsParams) => `Desactivar ${Str.pluralize('tasa', 'tasas', count)}`, enableRate: 'Activar tasa', - disableRate: 'Desactivar tasa', - disableRates: 'Desactivar tasas', - enableRates: 'Activar tasas', status: 'Estado', enabled: 'Activada', disabled: 'Desactivada', unit: 'Unidad', defaultCategory: 'Categoría predeterminada', + deleteDistanceRate: 'Eliminar tasa de distancia', + areYouSureDelete: ({count}: DistanceRateOperationsParams) => `¿Estás seguro de que quieres eliminar ${Str.pluralize('esta tasa', 'estas tasas', count)}?`, }, editor: { nameInputLabel: 'Nombre', diff --git a/src/languages/types.ts b/src/languages/types.ts index 93438d76885d..c365363f84af 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -295,6 +295,8 @@ type LogSizeParams = {size: number}; type HeldRequestParams = {comment: string}; +type DistanceRateOperationsParams = {count: number}; + export type { AdminCanceledRequestParams, ApprovedAmountParams, @@ -313,6 +315,7 @@ export type { DeleteActionParams, DeleteConfirmationParams, DidSplitAmountMessageParams, + DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnglishTranslation, diff --git a/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts new file mode 100644 index 000000000000..d4f972ff9757 --- /dev/null +++ b/src/libs/API/parameters/DeletePolicyDistanceRatesParams.ts @@ -0,0 +1,7 @@ +type DeletePolicyDistanceRatesParams = { + policyID: string; + customUnitID: string; + customUnitRateID: string[]; +}; + +export default DeletePolicyDistanceRatesParams; diff --git a/src/libs/API/parameters/SetPolicyDistanceRatesEnabledParams.ts b/src/libs/API/parameters/SetPolicyDistanceRatesEnabledParams.ts new file mode 100644 index 000000000000..95f5e61448d4 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyDistanceRatesEnabledParams.ts @@ -0,0 +1,7 @@ +type SetPolicyDistanceRatesEnabledParams = { + policyID: string; + customUnitID: string; + customUnitRateArray: string; +}; + +export default SetPolicyDistanceRatesEnabledParams; diff --git a/src/libs/API/parameters/UpdatePolicyDistanceRateValueParams.ts b/src/libs/API/parameters/UpdatePolicyDistanceRateValueParams.ts new file mode 100644 index 000000000000..c16487b3da60 --- /dev/null +++ b/src/libs/API/parameters/UpdatePolicyDistanceRateValueParams.ts @@ -0,0 +1,7 @@ +type UpdatePolicyDistanceRateValueParams = { + policyID: string; + customUnitID: string; + customUnitRateArray: string; +}; + +export default UpdatePolicyDistanceRateValueParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 0049489f1fc2..40f9c65f4ffd 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -184,6 +184,9 @@ export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMore export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams'; export type {default as SetPolicyDistanceRatesUnitParams} from './SetPolicyDistanceRatesUnitParams'; export type {default as SetPolicyDistanceRatesDefaultCategoryParams} from './SetPolicyDistanceRatesDefaultCategoryParams'; +export type {default as UpdatePolicyDistanceRateValueParams} from './UpdatePolicyDistanceRateValueParams'; +export type {default as SetPolicyDistanceRatesEnabledParams} from './SetPolicyDistanceRatesEnabledParams'; +export type {default as DeletePolicyDistanceRatesParams} from './DeletePolicyDistanceRatesParams'; export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams'; export type {default as SetPolicyTaxesEnabledParams} from './SetPolicyTaxesEnabledParams'; export type {default as DeletePolicyTaxesParams} from './DeletePolicyTaxesParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b83ffd89dcb1..709da3f353b1 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -190,6 +190,9 @@ const WRITE_COMMANDS = { CREATE_POLICY_DISTANCE_RATE: 'CreatePolicyDistanceRate', SET_POLICY_DISTANCE_RATES_UNIT: 'SetPolicyDistanceRatesUnit', SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY: 'SetPolicyDistanceRatesDefaultCategory', + UPDATE_POLICY_DISTANCE_RATE_VALUE: 'UpdatePolicyDistanceRateValue', + SET_POLICY_DISTANCE_RATES_ENABLED: 'SetPolicyDistanceRatesEnabled', + DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates', } as const; type WriteCommand = ValueOf; @@ -378,6 +381,9 @@ type WriteCommandParameters = { [WRITE_COMMANDS.RENAME_POLICY_TAX]: Parameters.RenamePolicyTaxParams; [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_UNIT]: Parameters.SetPolicyDistanceRatesUnitParams; [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY]: Parameters.SetPolicyDistanceRatesDefaultCategoryParams; + [WRITE_COMMANDS.UPDATE_POLICY_DISTANCE_RATE_VALUE]: Parameters.UpdatePolicyDistanceRateValueParams; + [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED]: Parameters.SetPolicyDistanceRatesEnabledParams; + [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index bd5bfc46134a..b9bc9e14293c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -265,6 +265,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/categories/EditCategoryPage').default as React.ComponentType, [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE]: () => require('../../../pages/workspace/distanceRates/CreateDistanceRatePage').default as React.ComponentType, [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: () => require('../../../pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS]: () => require('../../../pages/workspace/distanceRates/PolicyDistanceRateDetailsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATE_EDIT]: () => require('../../../pages/workspace/distanceRates/PolicyDistanceRateEditPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_SETTINGS]: () => require('../../../pages/workspace/tags/TagSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagsPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index 17f5049aab91..35b129e8b0c0 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -24,7 +24,12 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { ], [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT, SCREENS.WORKSPACE.TAG_CREATE, SCREENS.WORKSPACE.TAG_SETTINGS, SCREENS.WORKSPACE.TAG_EDIT], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS, SCREENS.WORKSPACE.CATEGORY_EDIT], - [SCREENS.WORKSPACE.DISTANCE_RATES]: [SCREENS.WORKSPACE.CREATE_DISTANCE_RATE, SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS], + [SCREENS.WORKSPACE.DISTANCE_RATES]: [ + SCREENS.WORKSPACE.CREATE_DISTANCE_RATE, + SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS, + SCREENS.WORKSPACE.DISTANCE_RATE_EDIT, + SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS, + ], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 130fdf23732f..d86c7190fc7a 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -295,6 +295,12 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS]: { path: ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.route, }, + [SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS]: { + path: ROUTES.WORKSPACE_DISTANCE_RATE_DETAILS.route, + }, + [SCREENS.WORKSPACE.DISTANCE_RATE_EDIT]: { + path: ROUTES.WORKSPACE_DISTANCE_RATE_EDIT.route, + }, [SCREENS.WORKSPACE.TAGS_SETTINGS]: { path: ROUTES.WORKSPACE_TAGS_SETTINGS.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 9b0d9ce4decc..851e8ffea59a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -182,6 +182,14 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.TAG_CREATE]: { policyID: string; }; + [SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS]: { + policyID: string; + rateID: string; + }; + [SCREENS.WORKSPACE.DISTANCE_RATE_EDIT]: { + policyID: string; + rateID: string; + }; [SCREENS.WORKSPACE.TAGS_SETTINGS]: { policyID: string; }; diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index 8f1438b1d092..cdcfb13eeb72 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -5,7 +5,7 @@ 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; +type RateValueForm = typeof ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM | typeof ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM | typeof ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_EDIT_FORM; function validateRateValue(values: FormOnyxValues, currency: string, toLocaleDigit: (arg: string) => string): FormInputErrors { const errors: FormInputErrors = {}; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index efd627e7ef93..1f48525e3378 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, EnablePolicyCategoriesParams, @@ -35,6 +36,7 @@ import type { OpenWorkspaceParams, OpenWorkspaceReimburseViewParams, SetPolicyDistanceRatesDefaultCategoryParams, + SetPolicyDistanceRatesEnabledParams, SetPolicyDistanceRatesUnitParams, SetWorkspaceApprovalModeParams, SetWorkspaceAutoReportingFrequencyParams, @@ -42,6 +44,7 @@ import type { SetWorkspaceAutoReportingParams, SetWorkspacePayerParams, SetWorkspaceReimbursementParams, + UpdatePolicyDistanceRateValueParams, UpdateWorkspaceAvatarParams, UpdateWorkspaceCustomUnitAndRateParams, UpdateWorkspaceDescriptionParams, @@ -3997,6 +4000,34 @@ function clearPolicyDistanceRatesErrorFields(policyID: string, customUnitID: str }); } +function clearDeleteDistanceRateError(policyID: string, customUnitID: string, rateID: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + customUnits: { + [customUnitID]: { + rates: { + [rateID]: { + errors: null, + }, + }, + }, + }, + }); +} + +function clearPolicyDistanceRateErrorFields(policyID: string, customUnitID: string, rateID: string, updatedErrorFields: ErrorFields) { + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + customUnits: { + [customUnitID]: { + rates: { + [rateID]: { + errorFields: updatedErrorFields, + }, + }, + }, + }, + }); +} + function setPolicyDistanceRatesUnit(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: CustomUnit) { const optimisticData: OnyxUpdate[] = [ { @@ -4020,7 +4051,7 @@ function setPolicyDistanceRatesUnit(policyID: string, currentCustomUnit: CustomU value: { customUnits: { [newCustomUnit.customUnitID]: { - pendingFields: null, + pendingFields: {attributes: null}, }, }, }, @@ -4105,6 +4136,237 @@ function setPolicyDistanceRatesDefaultCategory(policyID: string, currentCustomUn API.write(WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY, params, {optimisticData, successData, failureData}); } +/** + * Takes array of customUnitRates and removes pendingFields and errorFields from each rate - we don't want to send those via API + */ +function prepareCustomUnitRatesArray(customUnitRates: Rate[]): Rate[] { + const customUnitRateArray: Rate[] = []; + customUnitRates.forEach((rate) => { + const cleanedRate = {...rate}; + delete cleanedRate.pendingFields; + delete cleanedRate.errorFields; + customUnitRateArray.push(cleanedRate); + }); + + return customUnitRateArray; +} + +function updatePolicyDistanceRateValue(policyID: string, customUnit: CustomUnit, customUnitRates: Rate[]) { + const currentRates = customUnit.rates; + const optimisticRates: Record = {}; + const successRates: Record = {}; + const failureRates: Record = {}; + const rateIDs = customUnitRates.map((rate) => rate.customUnitRateID); + + for (const rateID of Object.keys(customUnit.rates)) { + if (rateIDs.includes(rateID)) { + const foundRate = customUnitRates.find((rate) => rate.customUnitRateID === rateID); + optimisticRates[rateID] = {...foundRate, pendingFields: {rate: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}}; + successRates[rateID] = {...foundRate, pendingFields: {rate: null}}; + failureRates[rateID] = { + ...currentRates[rateID], + pendingFields: {rate: null}, + errorFields: {rate: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + }; + } + } + + 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: failureRates, + }, + }, + }, + }, + ]; + + const params: UpdatePolicyDistanceRateValueParams = { + policyID, + customUnitID: customUnit.customUnitID, + customUnitRateArray: JSON.stringify(prepareCustomUnitRatesArray(customUnitRates)), + }; + + API.write(WRITE_COMMANDS.UPDATE_POLICY_DISTANCE_RATE_VALUE, params, {optimisticData, successData, failureData}); +} + +function setPolicyDistanceRatesEnabled(policyID: string, customUnit: CustomUnit, customUnitRates: Rate[]) { + const currentRates = customUnit.rates; + const optimisticRates: Record = {}; + const successRates: Record = {}; + const failureRates: Record = {}; + const rateIDs = customUnitRates.map((rate) => rate.customUnitRateID); + + for (const rateID of Object.keys(currentRates)) { + if (rateIDs.includes(rateID)) { + const foundRate = customUnitRates.find((rate) => rate.customUnitRateID === rateID); + optimisticRates[rateID] = {...foundRate, pendingFields: {enabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}}; + successRates[rateID] = {...foundRate, pendingFields: {enabled: null}}; + failureRates[rateID] = { + ...currentRates[rateID], + pendingFields: {enabled: null}, + errorFields: {enabled: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')}, + }; + } + } + + 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: failureRates, + }, + }, + }, + }, + ]; + + const params: SetPolicyDistanceRatesEnabledParams = { + policyID, + customUnitID: customUnit.customUnitID, + customUnitRateArray: JSON.stringify(prepareCustomUnitRatesArray(customUnitRates)), + }; + + API.write(WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED, params, {optimisticData, successData, failureData}); +} + +function deletePolicyDistanceRates(policyID: string, customUnit: CustomUnit, rateIDsToDelete: string[]) { + const currentRates = customUnit.rates; + const optimisticRates: Record = {}; + const successRates: Record = {}; + const failureRates: Record = {}; + + for (const rateID of Object.keys(currentRates)) { + if (rateIDsToDelete.includes(rateID)) { + optimisticRates[rateID] = { + ...currentRates[rateID], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }; + failureRates[rateID] = { + ...currentRates[rateID], + pendingAction: null, + errors: ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage'), + }; + } else { + optimisticRates[rateID] = currentRates[rateID]; + successRates[rateID] = currentRates[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: failureRates, + }, + }, + }, + }, + ]; + + const params: DeletePolicyDistanceRatesParams = { + policyID, + customUnitID: customUnit.customUnitID, + customUnitRateID: rateIDsToDelete, + }; + + API.write(WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES, params, {optimisticData, successData, failureData}); +} + function setPolicyCustomTaxName(policyID: string, customTaxName: string) { const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; const originalCustomTaxName = policy?.taxRates?.name; @@ -4335,6 +4597,7 @@ export { generateCustomUnitID, createPolicyDistanceRate, clearCreateDistanceRateItemAndError, + clearDeleteDistanceRateError, setPolicyDistanceRatesUnit, setPolicyDistanceRatesDefaultCategory, createPolicyTag, @@ -4350,4 +4613,8 @@ export { clearPolicyErrorField, isCurrencySupportedForDirectReimbursement, clearPolicyDistanceRatesErrorFields, + clearPolicyDistanceRateErrorFields, + updatePolicyDistanceRateValue, + setPolicyDistanceRatesEnabled, + deletePolicyDistanceRates, }; diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index d6f9ea29ac83..0a361f3f8e85 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -15,6 +15,7 @@ import validateRateValue from '@libs/PolicyDistanceRatesUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import {createPolicyDistanceRate, generateCustomUnitID} from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -33,6 +34,7 @@ type CreateDistanceRatePageProps = CreateDistanceRatePageOnyxProps & StackScreen function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); + const policyID = route.params.policyID; const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD; const customUnits = policy?.customUnits ?? {}; const customUnitID = customUnits[Object.keys(customUnits)[0]]?.customUnitID ?? ''; @@ -53,40 +55,45 @@ function CreateDistanceRatePage({policy, route}: CreateDistanceRatePageProps) { enabled: true, }; - createPolicyDistanceRate(route.params.policyID, customUnitID, newRate); + createPolicyDistanceRate(policyID, customUnitID, newRate); Navigation.goBack(); }; return ( - - - + + - - - - - + + + + + + ); diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx new file mode 100644 index 000000000000..965096ffa529 --- /dev/null +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -0,0 +1,176 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import ConfirmModal from '@components/ConfirmModal'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Switch from '@components/Switch'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; +import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Rate} from '@src/types/onyx/Policy'; + +type PolicyDistanceRateDetailsPageOnyxProps = { + /** Policy details */ + policy: OnyxEntry; +}; + +type PolicyDistanceRateDetailsPageProps = PolicyDistanceRateDetailsPageOnyxProps & StackScreenProps; + +function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetailsPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {windowWidth} = useWindowDimensions(); + const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + + const policyID = route.params.policyID; + const rateID = route.params.rateID; + const customUnits = policy?.customUnits ?? {}; + const customUnit = customUnits[Object.keys(customUnits)[0]]; + const rate = customUnit.rates[rateID]; + const currency = rate.currency ?? CONST.CURRENCY.USD; + const canDeleteRate = Object.values(customUnit.rates).filter((distanceRate) => distanceRate.enabled).length > 1 || !rate.enabled; + const canDisableRate = Object.values(customUnit.rates).filter((distanceRate) => distanceRate.enabled).length > 1; + const errorFields = rate.errorFields; + + const editRateValue = () => { + Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATE_EDIT.getRoute(policyID, rateID)); + }; + + const toggleRate = () => { + if (!rate.enabled || canDisableRate) { + Policy.setPolicyDistanceRatesEnabled(policyID, customUnit, [{...rate, enabled: !rate.enabled}]); + } else { + setIsWarningModalVisible(true); + } + }; + + const deleteRate = () => { + Navigation.goBack(); + Policy.deletePolicyDistanceRates(policyID, customUnit, [rateID]); + setIsDeleteModalVisible(false); + }; + + const rateValueToDisplay = CurrencyUtils.convertAmountToDisplayString(rate.rate, currency); + const unitToDisplay = translate(`common.${customUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}`); + + const threeDotsMenuItems = [ + { + icon: Expensicons.Trashcan, + text: translate('workspace.distanceRates.deleteDistanceRate'), + onSelected: () => { + if (canDeleteRate) { + setIsDeleteModalVisible(true); + return; + } + setIsWarningModalVisible(true); + }, + }, + ]; + + const clearErrorFields = (fieldName: keyof Rate) => { + Policy.clearPolicyDistanceRateErrorFields(policyID, customUnit.customUnitID, rateID, {...errorFields, [fieldName]: null}); + }; + + return ( + + + + + + + clearErrorFields('enabled')} + > + + {translate('workspace.distanceRates.enableRate')} + + + + clearErrorFields('rate')} + > + + + setIsWarningModalVisible(false)} + isVisible={isWarningModalVisible} + title={translate('workspace.distanceRates.oopsNotSoFast')} + prompt={translate('workspace.distanceRates.workspaceNeeds')} + confirmText={translate('common.buttonConfirm')} + shouldShowCancelButton={false} + /> + setIsDeleteModalVisible(false)} + prompt={translate('workspace.distanceRates.areYouSureDelete', {count: 1})} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> + + + + + + ); +} + +PolicyDistanceRateDetailsPage.displayName = 'PolicyDistanceRateDetailsPage'; + +export default withOnyx({ + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, +})(PolicyDistanceRateDetailsPage); diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx new file mode 100644 index 000000000000..be85ee680d36 --- /dev/null +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx @@ -0,0 +1,112 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {Keyboard} from 'react-native'; +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 {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 Navigation from '@libs/Navigation/Navigation'; +import validateRateValue from '@libs/PolicyDistanceRatesUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; +import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; +import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import * as Policy 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/PolicyDistanceRateEditForm'; +import type * as OnyxTypes from '@src/types/onyx'; + +type PolicyDistanceRateEditPageOnyxProps = { + /** Policy details */ + policy: OnyxEntry; +}; + +type PolicyDistanceRateEditPageProps = PolicyDistanceRateEditPageOnyxProps & StackScreenProps; + +function PolicyDistanceRateEditPage({policy, route}: PolicyDistanceRateEditPageProps) { + const styles = useThemeStyles(); + const {translate, toLocaleDigit} = useLocalize(); + const {inputCallbackRef} = useAutoFocusInput(); + + const policyID = route.params.policyID; + const rateID = route.params.rateID; + const customUnits = policy?.customUnits ?? {}; + const customUnit = customUnits[Object.keys(customUnits)[0]]; + const rate = customUnit.rates[rateID]; + const currency = rate.currency ?? CONST.CURRENCY.USD; + const currentRateValue = (rate.rate ?? 0).toString(); + + const submitRate = (values: FormOnyxValues) => { + Policy.updatePolicyDistanceRateValue(policyID, customUnit, [{...rate, rate: Number(values.rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET}]); + Keyboard.dismiss(); + Navigation.goBack(); + }; + + const validate = useCallback( + (values: FormOnyxValues) => validateRateValue(values, currency, toLocaleDigit), + [currency, toLocaleDigit], + ); + + return ( + + + + + Navigation.goBack()} + /> + + + + + + + + ); +} + +PolicyDistanceRateEditPage.displayName = 'PolicyDistanceRateEditPage'; + +export default withOnyx({ + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, +})(PolicyDistanceRateEditPage); diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 32531da0dd25..93accdb10b28 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -51,6 +51,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const {translate} = useLocalize(); const [selectedDistanceRates, setSelectedDistanceRates] = useState([]); const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const dropdownButtonRef = useRef(null); const policyID = route.params.policyID; @@ -59,6 +60,10 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) [policy?.customUnits], ); const customUnitRates: Record = useMemo(() => customUnit?.rates ?? {}, [customUnit]); + const canDeleteSelectedRates = selectedDistanceRates.length !== Object.values(customUnitRates).length; + const canDisableSelectedRates = Object.values(customUnitRates) + .filter((rate: Rate) => !selectedDistanceRates.includes(rate)) + .some((rate) => rate.enabled); function fetchDistanceRates() { Policy.openPolicyDistanceRatesPage(policyID); @@ -66,9 +71,14 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const dismissError = useCallback( (item: RateForList) => { + if (customUnitRates[item.value].errors) { + Policy.clearDeleteDistanceRateError(policyID, customUnit?.customUnitID ?? '', item.value); + return; + } + Policy.clearCreateDistanceRateItemAndError(policyID, customUnit?.customUnitID ?? '', item.value); }, - [customUnit?.customUnitID, policyID], + [customUnit?.customUnitID, customUnitRates, policyID], ); const {isOffline} = useNetwork({onReconnect: fetchDistanceRates}); @@ -87,7 +97,8 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) )}`, keyForList: value.customUnitRateID ?? '', isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined, - pendingAction: value.pendingAction, + isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingAction: value.pendingAction ?? value.pendingFields?.rate ?? value.pendingFields?.enabled, errors: value.errors ?? undefined, rightElement: ( @@ -114,30 +125,49 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(policyID)); }; - const editRate = () => { - // Navigation.navigate(ROUTES.WORKSPACE_EDIT_DISTANCE_RATE.getRoute(policyID, rateID)); + const openRateDetails = (rate: RateForList) => { + setSelectedDistanceRates([]); + Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATE_DETAILS.getRoute(policyID, rate.value)); }; const disableRates = () => { - if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { - // run enableWorkspaceDistanceRates for all selected rows + if (customUnit === undefined) { return; } - setIsWarningModalVisible(true); + Policy.setPolicyDistanceRatesEnabled( + policyID, + customUnit, + selectedDistanceRates.filter((rate) => rate.enabled).map((rate) => ({...rate, enabled: false})), + ); + setSelectedDistanceRates([]); }; const enableRates = () => { - // run enableWorkspaceDistanceRates for all selected rows + if (customUnit === undefined) { + return; + } + + Policy.setPolicyDistanceRatesEnabled( + policyID, + customUnit, + selectedDistanceRates.filter((rate) => !rate.enabled).map((rate) => ({...rate, enabled: true})), + ); + setSelectedDistanceRates([]); }; const deleteRates = () => { - if (selectedDistanceRates.length !== Object.values(customUnitRates).length) { - // run deleteWorkspaceDistanceRates for all selected rows + if (customUnit === undefined) { return; } - setIsWarningModalVisible(true); + Policy.deletePolicyDistanceRates( + policyID, + customUnit, + selectedDistanceRates.map((rate) => rate.customUnitRateID ?? ''), + ); + setSelectedDistanceRates([]); + setIsDeleteModalVisible(false); }; const toggleRate = (rate: RateForList) => { @@ -152,7 +182,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) if (selectedDistanceRates.length === Object.values(customUnitRates).length) { setSelectedDistanceRates([]); } else { - setSelectedDistanceRates([...Object.values(customUnitRates)]); + setSelectedDistanceRates([...Object.values(customUnitRates).filter((rate) => rate.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)]); } }; @@ -166,27 +196,27 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const getBulkActionsButtonOptions = () => { const options: Array> = [ { - text: translate(`workspace.distanceRates.${selectedDistanceRates.length <= 1 ? 'deleteRate' : 'deleteRates'}`), + text: translate('workspace.distanceRates.deleteRates', {count: selectedDistanceRates.length}), value: CONST.POLICY.DISTANCE_RATES_BULK_ACTION_TYPES.DELETE, icon: Expensicons.Trashcan, - onSelected: deleteRates, + onSelected: () => (canDeleteSelectedRates ? setIsDeleteModalVisible(true) : setIsWarningModalVisible(true)), }, ]; const enabledRates = selectedDistanceRates.filter((rate) => rate.enabled); if (enabledRates.length > 0) { options.push({ - text: translate(`workspace.distanceRates.${enabledRates.length <= 1 ? 'disableRate' : 'disableRates'}`), + text: translate('workspace.distanceRates.disableRates', {count: enabledRates.length}), value: CONST.POLICY.DISTANCE_RATES_BULK_ACTION_TYPES.DISABLE, icon: Expensicons.DocumentSlash, - onSelected: disableRates, + onSelected: () => (canDisableSelectedRates ? disableRates() : setIsWarningModalVisible(true)), }); } const disabledRates = selectedDistanceRates.filter((rate) => !rate.enabled); if (disabledRates.length > 0) { options.push({ - text: translate(`workspace.distanceRates.${disabledRates.length <= 1 ? 'enableRate' : 'enableRates'}`), + text: translate('workspace.distanceRates.enableRates', {count: disabledRates.length}), value: CONST.POLICY.DISTANCE_RATES_BULK_ACTION_TYPES.ENABLE, icon: Expensicons.DocumentSlash, onSelected: enableRates, @@ -271,7 +301,7 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) canSelectMultiple sections={[{data: distanceRatesList, indexOffset: 0, isDisabled: false}]} onCheckboxPress={toggleRate} - onSelectRow={editRate} + onSelectRow={openRateDetails} onSelectAll={toggleAllRates} onDismissError={dismissError} showScrollIndicator @@ -288,6 +318,16 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) confirmText={translate('common.buttonConfirm')} shouldShowCancelButton={false} /> + setIsDeleteModalVisible(false)} + prompt={translate('workspace.distanceRates.areYouSureDelete', {count: selectedDistanceRates.length})} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx index 83b096db1301..dbfb853b38a0 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx @@ -1,5 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -9,6 +10,7 @@ import type {ListItem} from '@components/SelectionList/types'; import type {UnitItemType} from '@components/UnitPicker'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; @@ -47,7 +49,14 @@ function PolicyDistanceRatesSettingsPage({policy, route}: PolicyDistanceRatesSet }; const setNewCategory = (category: ListItem) => { - Policy.setPolicyDistanceRatesDefaultCategory(policyID, customUnit, {...customUnit, defaultCategory: category.text}); + if (!category.searchText) { + return; + } + + Policy.setPolicyDistanceRatesDefaultCategory(policyID, customUnit, { + ...customUnit, + defaultCategory: defaultCategory === category.searchText ? '' : category.searchText, + }); }; const clearErrorFields = (fieldName: keyof CustomUnit) => { @@ -55,10 +64,10 @@ function PolicyDistanceRatesSettingsPage({policy, route}: PolicyDistanceRatesSet }; return ( - - + + - clearErrorFields('attributes')} - > - - - {policy?.areCategoriesEnabled && ( + clearErrorFields('defaultCategory')} + onClose={() => clearErrorFields('attributes')} > - - )} + {policy?.areCategoriesEnabled && ( + clearErrorFields('defaultCategory')} + > + + + )} + diff --git a/src/types/form/PolicyDistanceRateEditForm.ts b/src/types/form/PolicyDistanceRateEditForm.ts new file mode 100644 index 000000000000..2c7cb97b08d8 --- /dev/null +++ b/src/types/form/PolicyDistanceRateEditForm.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 PolicyDistanceRateEditForm = Form< + InputID, + { + [INPUT_IDS.RATE]: string; + } +>; + +export type {PolicyDistanceRateEditForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index f8bc009b7172..1f305769cec7 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -46,4 +46,5 @@ export type {WorkspaceTaxNameForm} from './WorkspaceTaxNameForm'; export type {WorkspaceTaxValueForm} from './WorkspaceTaxValueForm'; export type {WorkspaceTaxCustomName} from './WorkspaceTaxCustomName'; export type {PolicyCreateDistanceRateForm} from './PolicyCreateDistanceRateForm'; +export type {PolicyDistanceRateEditForm} from './PolicyDistanceRateEditForm'; export type {default as Form} from './Form'; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index cc2688e7a137..5165fa2ee128 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -9,8 +9,9 @@ type Rate = OnyxCommon.OnyxValueWithOfflineFeedback<{ rate?: number; currency?: string; customUnitRateID?: string; - errors?: OnyxCommon.Errors; enabled?: boolean; + errors?: OnyxCommon.Errors; + errorFields?: OnyxCommon.ErrorFields; }>; type Attributes = {