diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 16a0fa5fb5e8..71b80d3655bb 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2445,6 +2445,15 @@ const ROUTES = { return `workspaces/${policyID}/hr/gusto/approval-mode` as const; }, }, + WORKSPACE_HR_GUSTO_FINAL_APPROVER: { + route: 'workspaces/:policyID/hr/gusto/final-approver', + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_HR_GUSTO_FINAL_APPROVER route'); + } + return `workspaces/${policyID}/hr/gusto/final-approver` as const; + }, + }, WORKSPACE_TAGS: { route: 'workspaces/:policyID/tags', getRoute: (policyID: string | undefined) => { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index ee891e0aa4b4..98f3d3a4a36e 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -671,6 +671,7 @@ const SCREENS = { PROFILE: 'Workspace_Overview', HR: 'Workspace_HR', HR_GUSTO_APPROVAL_MODE: 'Workspace_HR_Gusto_Approval_Mode', + HR_GUSTO_FINAL_APPROVER: 'Workspace_HR_Gusto_Final_Approver', COMPANY_CARDS: 'Workspace_CompanyCards', COMPANY_CARDS_BROKEN_CARD_FEED_CONNECTION: 'Workspace_CompanyCards_BrokenCardFeedConnection', COMPANY_CARDS_REFRESH_CARD_FEED_CONNECTION: 'Workspace_CompanyCards_RefreshCardFeedConnection', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index e8d32a6ad3ae..ebd7a90ec34f 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -915,6 +915,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Security/LockAccount/UnlockAccountPage').default, [SCREENS.SETTINGS.LOCK.FAILED_TO_LOCK_ACCOUNT]: () => require('../../../../pages/settings/Security/LockAccount/FailedToLockAccountPage').default, [SCREENS.WORKSPACE.HR_GUSTO_APPROVAL_MODE]: () => require('../../../../pages/workspace/hr/gusto/GustoApprovalModePage').default, + [SCREENS.WORKSPACE.HR_GUSTO_FINAL_APPROVER]: () => require('../../../../pages/workspace/hr/gusto/GustoFinalApproverPage').default, [SCREENS.WORKSPACE.REPORTS_DEFAULT_TITLE]: () => require('../../../../pages/workspace/reports/ReportsDefaultTitle').default, [SCREENS.WORKSPACE.RULES_AUTO_APPROVE_REPORTS_UNDER]: () => require('../../../../pages/workspace/rules/RulesAutoApproveReportsUnderPage').default, [SCREENS.WORKSPACE.RULES_RANDOM_REPORT_AUDIT]: () => require('../../../../pages/workspace/rules/RulesRandomReportAuditPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index f11d61afc7f6..fa2a043dc5fe 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -180,7 +180,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.HR_GUSTO_APPROVAL_MODE]: { path: ROUTES.WORKSPACE_HR_GUSTO_APPROVAL_MODE.route, }, + [SCREENS.WORKSPACE.HR_GUSTO_FINAL_APPROVER]: { + path: ROUTES.WORKSPACE_HR_GUSTO_FINAL_APPROVER.route, + }, [SCREENS.WORKSPACE.RULES_CUSTOM]: { path: ROUTES.RULES_CUSTOM.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 6361cf1d8512..19cb40ed5b71 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1459,6 +1459,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.HR_GUSTO_APPROVAL_MODE]: { policyID: string; }; + [SCREENS.WORKSPACE.HR_GUSTO_FINAL_APPROVER]: { + policyID: string; + }; [SCREENS.WORKSPACE.RULES_PROHIBITED_DEFAULT]: { policyID: string; }; diff --git a/src/libs/actions/connections/Gusto.ts b/src/libs/actions/connections/Gusto.ts index 1c434041ec81..0315b43d1f5b 100644 --- a/src/libs/actions/connections/Gusto.ts +++ b/src/libs/actions/connections/Gusto.ts @@ -82,6 +82,66 @@ function updateGustoApprovalMode( write(WRITE_COMMANDS.UPDATE_GUSTO_APPROVAL_MODE, {policyID, approvalMode}, {optimisticData, successData, failureData}); } -export {updateGustoApprovalMode}; +function updateGustoFinalApprover(policyID: string | undefined, finalApprover: string | null, currentFinalApprover?: string | null) { + if (!policyID) { + return; + } + + const previousFinalApprover = currentFinalApprover ?? null; + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + gusto: { + config: { + finalApprover, + pendingFields: {finalApprover: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + errorFields: {finalApprover: null}, + }, + }, + }, + }, + }, + ]; + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + gusto: { + config: { + pendingFields: {finalApprover: null}, + errorFields: {finalApprover: null}, + }, + }, + }, + }, + }, + ]; + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + gusto: { + config: { + finalApprover: previousFinalApprover, + pendingFields: {finalApprover: null}, + errorFields: {finalApprover: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + }, + }, + }, + ]; + + write(WRITE_COMMANDS.UPDATE_GUSTO_FINAL_APPROVER, {policyID, finalApprover}, {optimisticData, successData, failureData}); +} + +export {updateGustoApprovalMode, updateGustoFinalApprover}; export default getGustoSetupLink; diff --git a/src/pages/workspace/hr/WorkspaceHRPage.tsx b/src/pages/workspace/hr/WorkspaceHRPage.tsx index d83eb52782ac..2e35466ee60c 100644 --- a/src/pages/workspace/hr/WorkspaceHRPage.tsx +++ b/src/pages/workspace/hr/WorkspaceHRPage.tsx @@ -29,6 +29,7 @@ import {openPolicyHRPage} from '@libs/actions/PolicyConnections'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; +import {getDisplayNameOrDefault, getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {getIntegrationLastSuccessfulDate, isGustoConnected} from '@libs/PolicyUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import CONST from '@src/CONST'; @@ -123,6 +124,13 @@ function WorkspaceHRPage({ return translate('workspace.hr.gusto.notSet'); } }; + const getGustoFinalApproverDisplayName = (finalApprover?: string | null) => { + if (!finalApprover) { + return translate('workspace.hr.gusto.notSet'); + } + + return getDisplayNameOrDefault(getPersonalDetailByEmail(finalApprover), finalApprover, false); + }; let gustoRowRightComponent; if (!isConnected) { gustoRowRightComponent = ( @@ -200,16 +208,28 @@ function WorkspaceHRPage({ rightComponent={gustoRowRightComponent} /> {isConnected && ( - - Navigation.navigate(ROUTES.WORKSPACE_HR_GUSTO_APPROVAL_MODE.getRoute(policyID))} - /> - + <> + + Navigation.navigate(ROUTES.WORKSPACE_HR_GUSTO_APPROVAL_MODE.getRoute(policyID))} + /> + + + Navigation.navigate(ROUTES.WORKSPACE_HR_GUSTO_FINAL_APPROVER.getRoute(policyID))} + /> + + )} diff --git a/src/pages/workspace/hr/gusto/GustoFinalApproverPage.tsx b/src/pages/workspace/hr/gusto/GustoFinalApproverPage.tsx new file mode 100644 index 000000000000..4d670e254679 --- /dev/null +++ b/src/pages/workspace/hr/gusto/GustoFinalApproverPage.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import WorkspaceMembersSelectionList from '@components/WorkspaceMembersSelectionList'; +import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; +import usePolicy from '@hooks/usePolicy'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {updateGustoFinalApprover} from '@libs/actions/connections/Gusto'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import {isGustoConnected} from '@libs/PolicyUtils'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type GustoFinalApproverPageProps = PlatformStackScreenProps; + +function GustoFinalApproverPage({ + route: { + params: {policyID}, + }, +}: GustoFinalApproverPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {isBetaEnabled} = usePermissions(); + const policy = usePolicy(policyID); + const finalApprover = policy?.connections?.gusto?.config?.finalApprover ?? null; + + return ( + + + Navigation.goBack(ROUTES.WORKSPACE_HR.getRoute(policyID))} + /> + { + updateGustoFinalApprover(policyID, email, finalApprover); + Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.goBack(ROUTES.WORKSPACE_HR.getRoute(policyID))); + }} + /> + + + ); +} + +export default GustoFinalApproverPage;