From dff681dfb13a67bc6e8618936ea2288cc8340d1c Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:29:56 +0100 Subject: [PATCH 01/35] add edit tax modal --- src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../AppNavigator/ModalStackNavigators.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 4 + src/libs/PolicyUtils.ts | 8 +- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 97 +++++++++++++++++++ .../workspace/taxes/WorkspaceTaxesPage.tsx | 2 +- 8 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index fd30bb0a6ac9..5a9c0cc7ad2a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -601,6 +601,10 @@ const ROUTES = { route: 'workspace/:policyID/taxes/new', getRoute: (policyID: string) => `workspace/${policyID}/taxes/new` as const, }, + WORKSPACE_TAXES_EDIT: { + route: 'workspace/:policyID/tax/:taxID', + getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}` as const, + }, WORKSPACE_DISTANCE_RATES: { route: 'workspace/:policyID/distance-rates', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 5e3126dfe7f5..11c2d38f4361 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -221,6 +221,7 @@ const SCREENS = { TAGS_EDIT: 'Tags_Edit', TAXES: 'Workspace_Taxes', TAXES_NEW: 'Workspace_Taxes_New', + TAXES_EDIT: 'Workspace_Taxes_Edit', TAG_CREATE: 'Tag_Create', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 2a6a1a0dbb03..164dccbc10ad 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -277,6 +277,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType, [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () => require('../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAXES_NEW]: () => require('../../../pages/workspace/taxes/WorkspaceNewTaxPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES_EDIT]: () => require('../../../pages/workspace/taxes/WorkspaceEditTaxPage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index b759ff9e977e..d9fd1fc98c9c 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -318,6 +318,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.TAXES_NEW]: { path: ROUTES.WORKSPACE_TAXES_NEW.route, }, + [SCREENS.WORKSPACE.TAXES_EDIT]: { + path: ROUTES.WORKSPACE_TAXES_EDIT.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c8ea81b2b5a7..dafe451262d2 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -212,6 +212,10 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.TAXES_NEW]: { policyID: string; }; + [SCREENS.WORKSPACE.TAXES_EDIT]: { + policyID: string; + taxID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index d42ad0d56d77..fe6fec83730b 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,7 +4,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import Navigation from './Navigation/Navigation'; @@ -272,6 +272,11 @@ function goBackFromInvalidPolicy() { Navigation.navigateWithSwitchPolicyID({route: ROUTES.ALL_SETTINGS}); } +/** Get a tax with given ID from policy */ +function getTaxByID(policy: OnyxEntry, taxID: string): TaxRate | undefined { + return policy?.taxRates?.taxes?.[taxID ?? '']; +} + export { getActivePolicies, hasAccountingConnections, @@ -303,6 +308,7 @@ export { getPolicyMembersByIdWithoutCurrentUser, goBackFromInvalidPolicy, hasTaxRateError, + getTaxByID, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx new file mode 100644 index 000000000000..94524ae16cd7 --- /dev/null +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -0,0 +1,97 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +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 type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import type SCREENS from '@src/SCREENS'; + +type WorkspaceEditTaxPageBaseProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; + +function WorkspaceEditTaxPage({ + route: { + params: {taxID}, + }, + policy, +}: WorkspaceEditTaxPageBaseProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + const {windowWidth} = useWindowDimensions(); + + const toggle = () => {}; + + const threeDotsMenuItems = useMemo(() => { + const menuItems = [ + { + icon: Expensicons.Trashcan, + text: translate('common.delete'), + onSelected: () => {}, + }, + ]; + return menuItems; + }, [translate]); + + return ( + + + + + {taxID ? ( + // TODO: Extract it to a separate component or use a common one + + + Enable rate + + + + + + ) : null} + {}} + /> + {}} + /> + + + + ); +} + +WorkspaceEditTaxPage.displayName = 'WorkspaceEditTaxPage'; + +export default withPolicyAndFullscreenLoading(WorkspaceEditTaxPage); diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index b0436f20a522..cede2bd31e7d 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -182,7 +182,7 @@ function WorkspaceTaxesPage({policy, route}: WorkspaceTaxesPageProps) { canSelectMultiple sections={[{data: taxesList, indexOffset: 0, isDisabled: false}]} onCheckboxPress={toggleTax} - onSelectRow={() => {}} + onSelectRow={(tax: ListItem) => tax.keyForList && Navigation.navigate(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policy?.id ?? '', tax.keyForList))} onSelectAll={toggleAllTaxes} showScrollIndicator ListItem={TableListItem} From ff731cbbad482073e39f4638d4ca020a353ae75c Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:45:08 +0100 Subject: [PATCH 02/35] add enabling/disabling taxes --- .../parameters/SetPolicyTaxesEnabledParams.ts | 10 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 2 +- src/libs/actions/TaxRate.ts | 75 ++++++++++++++++++- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 10 ++- 6 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts diff --git a/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts new file mode 100644 index 000000000000..0bc8550cd01b --- /dev/null +++ b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts @@ -0,0 +1,10 @@ +type SetPolicyTaxesEnabledParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array<{taxCode: string, enabled: bool}> + */ + taxFields: string; +}; + +export default SetPolicyTaxesEnabledParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 643657e86614..bfe08dbab50f 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -176,3 +176,4 @@ export type {default as OpenPolicyWorkflowsPageParams} from './OpenPolicyWorkflo export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams'; export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams'; export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams'; +export type {default as SetPolicyTaxesEnabledParams} from './SetPolicyTaxesEnabledParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 12c7f3c3bd5a..271aec0ec9be 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -173,6 +173,7 @@ const WRITE_COMMANDS = { ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest', DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_TAX: 'CreatePolicyTax', + SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled', } as const; type WriteCommand = ValueOf; @@ -344,6 +345,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams; + [WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams; }; const READ_COMMANDS = { 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 2693f443d659..a7d3fad55788 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -13,7 +13,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { ], [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT, SCREENS.WORKSPACE.TAG_CREATE], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], - [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW], + [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW, SCREENS.WORKSPACE.TAXES_EDIT], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 770417e56fe2..3c1f777f315e 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -1,14 +1,22 @@ +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CreatePolicyTaxParams} from '@libs/API/parameters'; +import type {CreatePolicyTaxParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import * as ErrorUtils from '@src/libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {TaxRate} from '@src/types/onyx'; +import type {Policy, TaxRate} from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type {OnyxData} from '@src/types/onyx/Request'; +let allPolicies: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY, + waitForCollectionCallback: true, + callback: (value) => (allPolicies = value), +}); + /** * Get tax value with percentage */ @@ -111,4 +119,65 @@ function clearTaxRateError(policyID: string, taxID: string, pendingAction?: Pend }); } -export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage}; +type TaxRateEnabledMap = Record>; + +function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isEnabled: boolean) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const originalTaxes = {...policy?.taxRates?.taxes}; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesIDsToUpdate.reduce((acc, taxID) => { + acc[taxID] = {isDisabled: !isEnabled, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}; + return acc; + }, {} as TaxRateEnabledMap), + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesIDsToUpdate.reduce((acc, taxID) => { + acc[taxID] = {isDisabled: !isEnabled, pendingAction: null}; + return acc; + }, {} as TaxRateEnabledMap), + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesIDsToUpdate.reduce((acc, taxID) => { + acc[taxID] = {isDisabled: !!originalTaxes[taxID].isDisabled, pendingAction: null}; + return acc; + }, {} as TaxRateEnabledMap), + }, + }, + }, + ], + }; + + const parameters = { + policyID, + taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: taxID, enabled: isEnabled}))), + } satisfies SetPolicyTaxesEnabledParams; + + console.log({parameters}); + + API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); +} + +export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled}; diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 94524ae16cd7..22706f22faf1 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -10,6 +10,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {setPolicyTaxesEnabled} from '@libs/actions/TaxRate'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -29,7 +30,14 @@ function WorkspaceEditTaxPage({ const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); const {windowWidth} = useWindowDimensions(); - const toggle = () => {}; + const toggle = () => { + // TODO: Backend call doesn't exist yet + return; + if (!policy?.id || !currentTaxRate) { + return; + } + setPolicyTaxesEnabled(policy.id, [taxID], !currentTaxRate?.isDisabled); + }; const threeDotsMenuItems = useMemo(() => { const menuItems = [ From ea97afb6673f59c3112b9a85658538f6fb051c47 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:39:36 +0100 Subject: [PATCH 03/35] add deleting tax rates --- src/languages/en.ts | 2 + .../API/parameters/DeletePolicyTaxesParams.ts | 11 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/TaxRate.ts | 83 +++++++++++++++++-- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 28 ++++++- 6 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 src/libs/API/parameters/DeletePolicyTaxesParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 4ca7b1e059ab..1c5a6931e10b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1856,6 +1856,8 @@ export default { valuePercentageRange: 'Please enter a valid percentage between 0 and 100', genericFailureMessage: 'An error occurred while updating the tax rate, please try again.', }, + deleteTax: 'Delete tax', + deleteTaxConfirmation: 'Are you sure you want to delete this tax?', }, emptyWorkspace: { title: 'Create a workspace', diff --git a/src/libs/API/parameters/DeletePolicyTaxesParams.ts b/src/libs/API/parameters/DeletePolicyTaxesParams.ts new file mode 100644 index 000000000000..fe03d388a129 --- /dev/null +++ b/src/libs/API/parameters/DeletePolicyTaxesParams.ts @@ -0,0 +1,11 @@ +type DeletePolicyTaxesParams = { + policyID: string; + /** + * Stringified JSON object with type of following structure: + * Array + * Each element is a tax name + */ + taxCodes: string; +}; + +export default DeletePolicyTaxesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index bfe08dbab50f..6567a3e22ad0 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -177,3 +177,4 @@ export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDis export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams'; 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 271aec0ec9be..98e5d820363a 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -174,6 +174,7 @@ const WRITE_COMMANDS = { DECLINE_JOIN_REQUEST: 'DeclineJoinRequest', CREATE_POLICY_TAX: 'CreatePolicyTax', SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled', + DELETE_POLICY_TAXES: 'DeletePolicyTaxes', } as const; type WriteCommand = ValueOf; @@ -346,6 +347,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams; [WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams; [WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams; + [WRITE_COMMANDS.DELETE_POLICY_TAXES]: Parameters.DeletePolicyTaxesParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 3c1f777f315e..1bc1f3460af1 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -1,13 +1,13 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CreatePolicyTaxParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; +import type {CreatePolicyTaxParams, DeletePolicyTaxesParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import * as ErrorUtils from '@src/libs/ErrorUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, TaxRate} from '@src/types/onyx'; -import type {PendingAction} from '@src/types/onyx/OnyxCommon'; +import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type {OnyxData} from '@src/types/onyx/Request'; let allPolicies: OnyxCollection; @@ -99,7 +99,7 @@ function createWorkspaceTax(policyID: string, taxRate: TaxRate) { API.write(WRITE_COMMANDS.CREATE_POLICY_TAX, parameters, onyxData); } -function clearTaxRateError(policyID: string, taxID: string, pendingAction?: PendingAction) { +function clearTaxRateError(policyID: string, taxID: string, pendingAction?: OnyxCommon.PendingAction) { if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { taxRates: { @@ -175,9 +175,80 @@ function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isE taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: taxID, enabled: isEnabled}))), } satisfies SetPolicyTaxesEnabledParams; - console.log({parameters}); - API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); } -export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled}; +type TaxRateDeleteMap = Record< + string, + | (Pick & { + errors: OnyxCommon.Errors | null; + }) + | null +>; + +/** + * API call to delete policy taxes + * @param taxesToDelete A tax IDs array to delete + */ +function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const policyTaxRates = policy?.taxRates?.taxes; + + if (!policyTaxRates) { + throw new Error('Policy or tax rates not found'); + } + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesToDelete.reduce((acc, taxID) => { + acc[taxID] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, errors: null}; + return acc; + }, {} as TaxRateDeleteMap), + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesToDelete.reduce((acc, taxID) => { + acc[taxID] = null; + return acc; + }, {} as TaxRateDeleteMap), + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: taxesToDelete.reduce((acc, taxID) => { + acc[taxID] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.genericFailureMessage')}; + return acc; + }, {} as TaxRateDeleteMap), + }, + }, + }, + ], + }; + + const parameters = { + policyID, + taxCodes: JSON.stringify(taxesToDelete.map((taxID) => policyTaxRates[taxID].name)), + } as DeletePolicyTaxesParams; + + API.write(WRITE_COMMANDS.DELETE_POLICY_TAXES, parameters, onyxData); +} + +export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled, deletePolicyTaxes}; diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 22706f22faf1..26e36e7f9b38 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -1,6 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; +import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -10,7 +11,8 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {setPolicyTaxesEnabled} from '@libs/actions/TaxRate'; +import {deletePolicyTaxes, setPolicyTaxesEnabled} from '@libs/actions/TaxRate'; +import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -29,6 +31,7 @@ function WorkspaceEditTaxPage({ const {translate} = useLocalize(); const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); const {windowWidth} = useWindowDimensions(); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const toggle = () => { // TODO: Backend call doesn't exist yet @@ -39,12 +42,21 @@ function WorkspaceEditTaxPage({ setPolicyTaxesEnabled(policy.id, [taxID], !currentTaxRate?.isDisabled); }; + const deleteTax = () => { + if (!policy?.id) { + return; + } + deletePolicyTaxes(policy?.id, [taxID]); + setIsDeleteModalVisible(false); + Navigation.goBack(); + }; + const threeDotsMenuItems = useMemo(() => { const menuItems = [ { icon: Expensicons.Trashcan, text: translate('common.delete'), - onSelected: () => {}, + onSelected: () => setIsDeleteModalVisible(true), }, ]; return menuItems; @@ -96,6 +108,16 @@ function WorkspaceEditTaxPage({ /> + setIsDeleteModalVisible(false)} + prompt={translate('workspace.taxes.deleteTaxConfirmation')} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> ); } From f512cf09249c49f58410c90342df6ffa861cb9a9 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:25:43 +0100 Subject: [PATCH 04/35] update enable tax api call --- src/libs/actions/TaxRate.ts | 2 +- src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 1bc1f3460af1..665ceb1da22c 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -172,7 +172,7 @@ function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isE const parameters = { policyID, - taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: taxID, enabled: isEnabled}))), + taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: originalTaxes[taxID].name, enabled: isEnabled}))), } satisfies SetPolicyTaxesEnabledParams; API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 26e36e7f9b38..98d19ecc2b6b 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -34,12 +34,10 @@ function WorkspaceEditTaxPage({ const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const toggle = () => { - // TODO: Backend call doesn't exist yet - return; if (!policy?.id || !currentTaxRate) { return; } - setPolicyTaxesEnabled(policy.id, [taxID], !currentTaxRate?.isDisabled); + setPolicyTaxesEnabled(policy.id, [taxID], !!currentTaxRate?.isDisabled); }; const deleteTax = () => { From 2a3f4c33e8262d0e05dc731410e041f24e7d5261 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:39:42 +0100 Subject: [PATCH 05/35] add NamePage and ValuePage --- src/ONYXKEYS.ts | 4 + src/ROUTES.ts | 8 ++ src/SCREENS.ts | 2 + .../AppNavigator/ModalStackNavigators.tsx | 2 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 6 ++ src/libs/Navigation/types.ts | 8 ++ src/pages/workspace/taxes/NamePage.tsx | 85 ++++++++++++++++++ src/pages/workspace/taxes/ValuePage.tsx | 90 +++++++++++++++++++ .../workspace/taxes/WorkspaceEditTaxPage.tsx | 5 +- src/types/form/WorkspaceTaxNameForm.ts | 18 ++++ src/types/form/WorkspaceTaxValueForm.ts | 18 ++++ src/types/form/index.ts | 2 + 13 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/pages/workspace/taxes/NamePage.tsx create mode 100644 src/pages/workspace/taxes/ValuePage.tsx create mode 100644 src/types/form/WorkspaceTaxNameForm.ts create mode 100644 src/types/form/WorkspaceTaxValueForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b4de6c6ef258..900bb9804d29 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -411,6 +411,8 @@ const ONYXKEYS = { POLICY_TAG_NAME_FORM_DRAFT: 'policyTagNameFormDraft', WORKSPACE_NEW_TAX_FORM: 'workspaceNewTaxForm', WORKSPACE_NEW_TAX_FORM_DRAFT: 'workspaceNewTaxFormDraft', + WORKSPACE_TAX_NAME_FORM: 'workspaceTaxNameForm', + WORKSPACE_TAX_VALUE_FORM: 'workspaceTaxValueForm', }, } as const; @@ -459,6 +461,8 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; [ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm; [ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM]: FormTypes.WorkspaceNewTaxForm; + [ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm; + [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 5a9c0cc7ad2a..2487102bc504 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -605,6 +605,14 @@ const ROUTES = { route: 'workspace/:policyID/tax/:taxID', getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}` as const, }, + WORKSPACE_TAXES_NAME: { + route: 'workspace/:policyID/tax/:taxID/name', + getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}/name` as const, + }, + WORKSPACE_TAXES_VALUE: { + route: 'workspace/:policyID/tax/:taxID/value', + getRoute: (policyID: string, taxID: string) => `workspace/${policyID}/tax/${encodeURI(taxID)}/value` as const, + }, WORKSPACE_DISTANCE_RATES: { route: 'workspace/:policyID/distance-rates', getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 11c2d38f4361..d8eb643cefde 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -222,6 +222,8 @@ const SCREENS = { TAXES: 'Workspace_Taxes', TAXES_NEW: 'Workspace_Taxes_New', TAXES_EDIT: 'Workspace_Taxes_Edit', + TAXES_NAME: 'Workspace_Taxes_Name', + TAXES_VALUE: 'Workspace_Taxes_Value', TAG_CREATE: 'Tag_Create', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 164dccbc10ad..41fe1b298576 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -278,6 +278,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAXES_NEW]: () => require('../../../pages/workspace/taxes/WorkspaceNewTaxPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAXES_EDIT]: () => require('../../../pages/workspace/taxes/WorkspaceEditTaxPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES_NAME]: () => require('../../../pages/workspace/taxes/NamePage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES_VALUE]: () => require('../../../pages/workspace/taxes/ValuePage').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ 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 a7d3fad55788..9eb35121413c 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -13,7 +13,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { ], [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT, SCREENS.WORKSPACE.TAG_CREATE], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], - [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW, SCREENS.WORKSPACE.TAXES_EDIT], + [SCREENS.WORKSPACE.TAXES]: [SCREENS.WORKSPACE.TAXES_NEW, SCREENS.WORKSPACE.TAXES_EDIT, SCREENS.WORKSPACE.TAXES_NAME, SCREENS.WORKSPACE.TAXES_VALUE], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index d9fd1fc98c9c..cfef83bf9c1c 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -321,6 +321,12 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.TAXES_EDIT]: { path: ROUTES.WORKSPACE_TAXES_EDIT.route, }, + [SCREENS.WORKSPACE.TAXES_NAME]: { + path: ROUTES.WORKSPACE_TAXES_NAME.route, + }, + [SCREENS.WORKSPACE.TAXES_VALUE]: { + path: ROUTES.WORKSPACE_TAXES_VALUE.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index dafe451262d2..f93b52657acf 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -216,6 +216,14 @@ type SettingsNavigatorParamList = { policyID: string; taxID: string; }; + [SCREENS.WORKSPACE.TAXES_NAME]: { + policyID: string; + taxID: string; + }; + [SCREENS.WORKSPACE.TAXES_VALUE]: { + policyID: string; + taxID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/pages/workspace/taxes/NamePage.tsx b/src/pages/workspace/taxes/NamePage.tsx new file mode 100644 index 000000000000..d7626f5dca5c --- /dev/null +++ b/src/pages/workspace/taxes/NamePage.tsx @@ -0,0 +1,85 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import React, {useState} from 'react'; +import {View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {renamePolicyTax} from '@libs/actions/TaxRate'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceTaxNameForm'; +import type * as OnyxTypes from '@src/types/onyx'; + +type NamePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; + +const parser = new ExpensiMark(); + +function NamePage({ + route: { + params: {policyID, taxID}, + }, + policy, +}: NamePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + + const [name, setName] = useState(() => parser.htmlToMarkdown(currentTaxRate?.name ?? '')); + + const submit = () => { + renamePolicyTax(policyID, taxID, name); + Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + }; + + return ( + + + + + + + + + + ); +} + +NamePage.displayName = 'NamePage'; + +export default withPolicyAndFullscreenLoading(NamePage); diff --git a/src/pages/workspace/taxes/ValuePage.tsx b/src/pages/workspace/taxes/ValuePage.tsx new file mode 100644 index 000000000000..5a390f27dacf --- /dev/null +++ b/src/pages/workspace/taxes/ValuePage.tsx @@ -0,0 +1,90 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback, useState} from 'react'; +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 Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {renamePolicyTax, updatePolicyTaxValue} from '@libs/actions/TaxRate'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceTaxValueForm'; +import type * as OnyxTypes from '@src/types/onyx'; + +type ValuePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; + +function ValuePage({ + route: { + params: {policyID, taxID}, + }, + policy, +}: ValuePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + const [value, setValue] = useState(currentTaxRate?.value?.replace('%', '')); + + // TODO: Extract it to a separate file, and use it also when creating a new tax + const validate = useCallback((values: FormOnyxValues) => { + const errors = {}; + + if (Number(values.value) < 0 || Number(values.value) >= 100) { + ErrorUtils.addErrorMessage(errors, 'value', 'Percentage must be between 0 and 100'); + } + + return errors; + }, []); + + const submit = useCallback( + (values: FormOnyxValues) => { + updatePolicyTaxValue(policyID, taxID, `${values.value}%`); + Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + }, + [policyID, taxID], + ); + + return ( + + + + + %} + /> + + + ); +} + +ValuePage.displayName = 'ValuePage'; + +export default withPolicyAndFullscreenLoading(ValuePage); diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 98d19ecc2b6b..2617b710f55c 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -17,6 +17,7 @@ import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type WorkspaceEditTaxPageBaseProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -94,7 +95,7 @@ function WorkspaceEditTaxPage({ description={translate('common.name')} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => {}} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_TAXES_NAME.getRoute(`${policy?.id}`, taxID))} /> {}} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_TAXES_VALUE.getRoute(`${policy?.id}`, taxID))} /> diff --git a/src/types/form/WorkspaceTaxNameForm.ts b/src/types/form/WorkspaceTaxNameForm.ts new file mode 100644 index 000000000000..dfe01ab55fae --- /dev/null +++ b/src/types/form/WorkspaceTaxNameForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + NAME: 'name', +} as const; + +type InputID = ValueOf; + +type WorkspaceTaxNameForm = Form< + InputID, + { + [INPUT_IDS.NAME]: string; + } +>; + +export type {WorkspaceTaxNameForm}; +export default INPUT_IDS; diff --git a/src/types/form/WorkspaceTaxValueForm.ts b/src/types/form/WorkspaceTaxValueForm.ts new file mode 100644 index 000000000000..e53c6cd46cc2 --- /dev/null +++ b/src/types/form/WorkspaceTaxValueForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + VALUE: 'value', +} as const; + +type InputID = ValueOf; + +type WorkspaceTaxValueForm = Form< + InputID, + { + [INPUT_IDS.VALUE]: string; + } +>; + +export type {WorkspaceTaxValueForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 8beada7ad6a8..7df684ccbd3e 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -40,5 +40,7 @@ export type {ReportPhysicalCardForm} from './ReportPhysicalCardForm'; export type {WorkspaceDescriptionForm} from './WorkspaceDescriptionForm'; export type {PolicyTagNameForm} from './PolicyTagNameForm'; export type {WorkspaceNewTaxForm} from './WorkspaceNewTaxForm'; +export type {WorkspaceTaxNameForm} from './WorkspaceTaxNameForm'; +export type {WorkspaceTaxValueForm} from './WorkspaceTaxValueForm'; export type {WorkspaceTagCreateForm} from './WorkspaceTagCreateForm'; export type {default as Form} from './Form'; From a25926f7f41b07d74eddf4173fd90c145cd39109 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:23:02 +0100 Subject: [PATCH 06/35] renaming tax names --- .../parameters/UpdatePolicyTaxValueParams.ts | 7 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 3 + src/libs/actions/TaxRate.ts | 135 +++++++++++++++++- src/pages/workspace/taxes/ValuePage.tsx | 2 +- 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 src/libs/API/parameters/UpdatePolicyTaxValueParams.ts diff --git a/src/libs/API/parameters/UpdatePolicyTaxValueParams.ts b/src/libs/API/parameters/UpdatePolicyTaxValueParams.ts new file mode 100644 index 000000000000..1124755ea9ef --- /dev/null +++ b/src/libs/API/parameters/UpdatePolicyTaxValueParams.ts @@ -0,0 +1,7 @@ +type UpdatePolicyTaxValueParams = { + policyID: string; + taxCode: string; + taxAmount: number; +}; + +export default UpdatePolicyTaxValueParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index f67ea4690e7d..6ab1eed97d16 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -180,3 +180,4 @@ export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMore export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams'; export type {default as SetPolicyTaxesEnabledParams} from './SetPolicyTaxesEnabledParams'; export type {default as DeletePolicyTaxesParams} from './DeletePolicyTaxesParams'; +export type {default as UpdatePolicyTaxValueParams} from './UpdatePolicyTaxValueParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 5aa0f6a18599..ab2d17ebd4b9 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -176,6 +176,8 @@ const WRITE_COMMANDS = { CREATE_POLICY_TAX: 'CreatePolicyTax', SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled', DELETE_POLICY_TAXES: 'DeletePolicyTaxes', + UPDATE_POLICY_TAX_VALUE: 'UpdatePolicyTaxValue', + RENAME_POLICY_TAX: 'RenamePolicyTax', } as const; type WriteCommand = ValueOf; @@ -350,6 +352,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CREATE_POLICY_TAX]: Parameters.CreatePolicyTaxParams; [WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED]: Parameters.SetPolicyTaxesEnabledParams; [WRITE_COMMANDS.DELETE_POLICY_TAXES]: Parameters.DeletePolicyTaxesParams; + [WRITE_COMMANDS.UPDATE_POLICY_TAX_VALUE]: Parameters.UpdatePolicyTaxValueParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 665ceb1da22c..41c7800be5c6 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -1,7 +1,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CreatePolicyTaxParams, DeletePolicyTaxesParams, SetPolicyTaxesEnabledParams} from '@libs/API/parameters'; +import type {CreatePolicyTaxParams, DeletePolicyTaxesParams, SetPolicyTaxesEnabledParams, UpdatePolicyTaxValueParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import * as ErrorUtils from '@src/libs/ErrorUtils'; @@ -251,4 +251,135 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { API.write(WRITE_COMMANDS.DELETE_POLICY_TAXES, parameters, onyxData); } -export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled, deletePolicyTaxes}; +/** + * Rename policy tax + */ +function updatePolicyTaxValue(policyID: string, taxID: string, taxValue: number) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const originalTaxRate = {...policy?.taxRates?.taxes[taxID]}; + const stringTaxValue = `${taxValue}%`; + + console.log({policy, originalTaxRate, stringTaxValue, taxValue, taxID}); + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: { + value: stringTaxValue, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + errors: null, + }, + }, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {value: stringTaxValue, pendingAction: null, errors: null}, + }, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {value: originalTaxRate.value, pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.genericFailureMessage')}, + }, + }, + }, + }, + ], + }; + + if (!originalTaxRate.name) { + throw new Error('Tax rate name not found'); + } + + const parameters = { + policyID, + taxCode: originalTaxRate.name, + taxAmount: Number(taxValue), + } as UpdatePolicyTaxValueParams; + + API.write(WRITE_COMMANDS.UPDATE_POLICY_TAX_VALUE, parameters, onyxData); +} + +function renamePolicyTax(policyID: string, taxID: string, newName: string) { + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const originalTaxRate = {...policy?.taxRates?.taxes[taxID]}; + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: { + name: newName, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + errors: null, + }, + }, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {name: newName, pendingAction: null, errors: null}, + }, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + taxRates: { + taxes: { + [taxID]: {name: originalTaxRate.name, pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxError('workspace.taxes.genericFailureMessage')}, + }, + }, + }, + }, + ], + }; + + if (!originalTaxRate.name) { + throw new Error('Tax rate name not found'); + } + + const parameters = { + policyID, + taxCode: taxID, + newName, + }; + + API.write(WRITE_COMMANDS.RENAME_POLICY_TAX, parameters, onyxData); +} + +export {createWorkspaceTax, clearTaxRateError, getNextTaxID, getTaxValueWithPercentage, setPolicyTaxesEnabled, deletePolicyTaxes, updatePolicyTaxValue, renamePolicyTax}; diff --git a/src/pages/workspace/taxes/ValuePage.tsx b/src/pages/workspace/taxes/ValuePage.tsx index 5a390f27dacf..6c733968aa5e 100644 --- a/src/pages/workspace/taxes/ValuePage.tsx +++ b/src/pages/workspace/taxes/ValuePage.tsx @@ -48,7 +48,7 @@ function ValuePage({ const submit = useCallback( (values: FormOnyxValues) => { - updatePolicyTaxValue(policyID, taxID, `${values.value}%`); + updatePolicyTaxValue(policyID, taxID, Number(values.value)); Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); }, [policyID, taxID], From ac59d758b8c8bb69978fbfca5326c974acc8350a Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:45:03 +0100 Subject: [PATCH 07/35] update backend queries --- src/libs/actions/TaxRate.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 41c7800be5c6..50a8ed0a1bdc 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -86,14 +86,12 @@ function createWorkspaceTax(policyID: string, taxRate: TaxRate) { const parameters = { policyID, - taxFields: JSON.stringify([ - { - name: taxRate.name, - value: taxRate.value, - enabled: true, - taxCode: taxRate.code, - }, - ]), + taxFields: JSON.stringify({ + name: taxRate.name, + value: taxRate.value, + enabled: true, + taxCode: taxRate.code, + }), } satisfies CreatePolicyTaxParams; API.write(WRITE_COMMANDS.CREATE_POLICY_TAX, parameters, onyxData); @@ -313,7 +311,7 @@ function updatePolicyTaxValue(policyID: string, taxID: string, taxValue: number) const parameters = { policyID, - taxCode: originalTaxRate.name, + taxCode: taxID, taxAmount: Number(taxValue), } as UpdatePolicyTaxValueParams; From 76d56f8f74d33f8fa09184a470b8b993804aae10 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:40:08 +0100 Subject: [PATCH 08/35] refactor --- src/pages/workspace/taxes/NamePage.tsx | 22 +++++++++++++--------- src/pages/workspace/taxes/ValuePage.tsx | 14 +++++++++----- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/pages/workspace/taxes/NamePage.tsx b/src/pages/workspace/taxes/NamePage.tsx index d7626f5dca5c..c6c4f04177f1 100644 --- a/src/pages/workspace/taxes/NamePage.tsx +++ b/src/pages/workspace/taxes/NamePage.tsx @@ -1,12 +1,13 @@ import type {StackScreenProps} from '@react-navigation/stack'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import React, {useState} from 'react'; +import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {renamePolicyTax} from '@libs/actions/TaxRate'; @@ -20,7 +21,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/WorkspaceTaxNameForm'; -import type * as OnyxTypes from '@src/types/onyx'; type NamePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -35,12 +35,15 @@ function NamePage({ const styles = useThemeStyles(); const {translate} = useLocalize(); const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); + const {inputCallbackRef} = useAutoFocusInput(); const [name, setName] = useState(() => parser.htmlToMarkdown(currentTaxRate?.name ?? '')); + const goBack = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)), [policyID, taxID]); + const submit = () => { renamePolicyTax(policyID, taxID, name); - Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + goBack(); }; return ( @@ -49,7 +52,10 @@ function NamePage({ shouldEnableMaxHeight testID={NamePage.displayName} > - + diff --git a/src/pages/workspace/taxes/ValuePage.tsx b/src/pages/workspace/taxes/ValuePage.tsx index 6c733968aa5e..3aa990db13c3 100644 --- a/src/pages/workspace/taxes/ValuePage.tsx +++ b/src/pages/workspace/taxes/ValuePage.tsx @@ -9,7 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {renamePolicyTax, updatePolicyTaxValue} from '@libs/actions/TaxRate'; +import {updatePolicyTaxValue} from '@libs/actions/TaxRate'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -20,7 +20,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/WorkspaceTaxValueForm'; -import type * as OnyxTypes from '@src/types/onyx'; type ValuePageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; @@ -35,6 +34,8 @@ function ValuePage({ const currentTaxRate = PolicyUtils.getTaxByID(policy, taxID); const [value, setValue] = useState(currentTaxRate?.value?.replace('%', '')); + const goBack = useCallback(() => Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)), [policyID, taxID]); + // TODO: Extract it to a separate file, and use it also when creating a new tax const validate = useCallback((values: FormOnyxValues) => { const errors = {}; @@ -49,9 +50,9 @@ function ValuePage({ const submit = useCallback( (values: FormOnyxValues) => { updatePolicyTaxValue(policyID, taxID, Number(values.value)); - Navigation.goBack(ROUTES.WORKSPACE_TAXES_EDIT.getRoute(policyID ?? '', taxID)); + goBack(); }, - [policyID, taxID], + [goBack, policyID, taxID], ); return ( @@ -60,7 +61,10 @@ function ValuePage({ shouldEnableMaxHeight testID={ValuePage.displayName} > - + Date: Thu, 14 Mar 2024 12:45:10 +0100 Subject: [PATCH 09/35] update to new backend --- src/libs/API/parameters/DeletePolicyTaxesParams.ts | 2 +- src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts | 2 +- src/libs/actions/TaxRate.ts | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libs/API/parameters/DeletePolicyTaxesParams.ts b/src/libs/API/parameters/DeletePolicyTaxesParams.ts index fe03d388a129..9e0963cdcb28 100644 --- a/src/libs/API/parameters/DeletePolicyTaxesParams.ts +++ b/src/libs/API/parameters/DeletePolicyTaxesParams.ts @@ -5,7 +5,7 @@ type DeletePolicyTaxesParams = { * Array * Each element is a tax name */ - taxCodes: string; + taxNames: string; }; export default DeletePolicyTaxesParams; diff --git a/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts index 0bc8550cd01b..4ed0a05cfdec 100644 --- a/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts +++ b/src/libs/API/parameters/SetPolicyTaxesEnabledParams.ts @@ -4,7 +4,7 @@ type SetPolicyTaxesEnabledParams = { * Stringified JSON object with type of following structure: * Array<{taxCode: string, enabled: bool}> */ - taxFields: string; + taxFieldsArray: string; }; export default SetPolicyTaxesEnabledParams; diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 50a8ed0a1bdc..ced92e12e4b8 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -170,7 +170,7 @@ function setPolicyTaxesEnabled(policyID: string, taxesIDsToUpdate: string[], isE const parameters = { policyID, - taxFields: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: originalTaxes[taxID].name, enabled: isEnabled}))), + taxFieldsArray: JSON.stringify(taxesIDsToUpdate.map((taxID) => ({taxCode: originalTaxes[taxID].name, enabled: isEnabled}))), } satisfies SetPolicyTaxesEnabledParams; API.write(WRITE_COMMANDS.SET_POLICY_TAXES_ENABLED, parameters, onyxData); @@ -243,7 +243,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { const parameters = { policyID, - taxCodes: JSON.stringify(taxesToDelete.map((taxID) => policyTaxRates[taxID].name)), + taxNames: JSON.stringify(taxesToDelete.map((taxID) => policyTaxRates[taxID].name)), } as DeletePolicyTaxesParams; API.write(WRITE_COMMANDS.DELETE_POLICY_TAXES, parameters, onyxData); @@ -257,8 +257,6 @@ function updatePolicyTaxValue(policyID: string, taxID: string, taxValue: number) const originalTaxRate = {...policy?.taxRates?.taxes[taxID]}; const stringTaxValue = `${taxValue}%`; - console.log({policy, originalTaxRate, stringTaxValue, taxValue, taxID}); - const onyxData: OnyxData = { optimisticData: [ { From 3b590c672aa27e32ef4f76c9c272e9126a9834e5 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Thu, 14 Mar 2024 13:07:26 +0100 Subject: [PATCH 10/35] add bulk actions --- src/CONST.ts | 5 ++ .../ButtonWithDropdownMenu/types.ts | 4 +- src/languages/en.ts | 6 +- .../workspace/taxes/WorkspaceEditTaxPage.tsx | 2 +- .../workspace/taxes/WorkspaceTaxesPage.tsx | 73 +++++++++++++++---- 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index cf0d6ac57a08..bb04b27dc1a2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1422,6 +1422,11 @@ const CONST = { DISABLE: 'disable', ENABLE: 'enable', }, + TAX_RATES_BULK_ACTION_TYPES: { + DELETE: 'delete', + DISABLE: 'disable', + ENABLE: 'enable', + }, }, CUSTOM_UNITS: { diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index 798369292958..83100788761f 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -12,6 +12,8 @@ type WorkspaceMemberBulkActionType = DeepValueOf; +type WorkspaceTaxRatesBulkActionType = DeepValueOf; + type DropdownOption = { value: TValueType; text: string; @@ -73,4 +75,4 @@ type ButtonWithDropdownMenuProps = { wrapperStyle?: StyleProp; }; -export type {PaymentType, WorkspaceMemberBulkActionType, WorkspaceDistanceRatesBulkActionType, DropdownOption, ButtonWithDropdownMenuProps}; +export type {PaymentType, WorkspaceMemberBulkActionType, WorkspaceDistanceRatesBulkActionType, DropdownOption, ButtonWithDropdownMenuProps, WorkspaceTaxRatesBulkActionType}; diff --git a/src/languages/en.ts b/src/languages/en.ts index d60861a838be..876399e9a864 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1860,8 +1860,12 @@ export default { valuePercentageRange: 'Please enter a valid percentage between 0 and 100', genericFailureMessage: 'An error occurred while updating the tax rate, please try again.', }, - deleteTax: 'Delete tax', deleteTaxConfirmation: 'Are you sure you want to delete this tax?', + actions: { + delete: 'Delete rate', + disable: 'Disable rate', + enable: 'Enable rate', + }, }, emptyWorkspace: { title: 'Create a workspace', diff --git a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx index 2617b710f55c..e785790d64e4 100644 --- a/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx @@ -108,7 +108,7 @@ function WorkspaceEditTaxPage({ setIsDeleteModalVisible(false)} diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index cede2bd31e7d..04529014b2ac 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -2,6 +2,8 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import Button from '@components/Button'; +import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; +import type {DropdownOption, WorkspaceTaxRatesBulkActionType} from '@components/ButtonWithDropdownMenu/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -129,23 +131,64 @@ function WorkspaceTaxesPage({policy, route}: WorkspaceTaxesPageProps) { ); + const dropdownMenuOptions = useMemo(() => { + const options: Array> = [ + { + icon: Expensicons.Trashcan, + text: translate('workspace.taxes.actions.delete'), + value: CONST.POLICY.TAX_RATES_BULK_ACTION_TYPES.DELETE, + onSelected: () => {}, + }, + ]; + + // `Disable rates` when at least one enabled rate is selected. + if (selectedTaxesIDs.some((taxID) => !policy?.taxRates?.taxes[taxID]?.isDisabled)) { + options.push({ + icon: Expensicons.Document, + text: translate('workspace.taxes.actions.disable'), + value: CONST.POLICY.TAX_RATES_BULK_ACTION_TYPES.DISABLE, + }); + } + + // `Enable rates` when at least one disabled rate is selected. + if (selectedTaxesIDs.some((taxID) => policy?.taxRates?.taxes[taxID]?.isDisabled)) { + options.push({ + icon: Expensicons.Document, + text: translate('workspace.taxes.actions.enable'), + value: CONST.POLICY.TAX_RATES_BULK_ACTION_TYPES.ENABLE, + }); + } + return options; + }, [policy?.taxRates?.taxes, selectedTaxesIDs, translate]); + const headerButtons = ( -