From f30d0de80b66679dd2fd35e6759593927831e734 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 6 May 2026 01:45:40 +0530 Subject: [PATCH 01/12] Add Gusto approval settings --- src/ROUTES.ts | 18 +++ src/SCREENS.ts | 2 + src/languages/de.ts | 19 +++ src/languages/en.ts | 19 +++ src/languages/es.ts | 19 +++ src/languages/fr.ts | 19 +++ src/languages/it.ts | 19 +++ src/languages/ja.ts | 19 +++ src/languages/nl.ts | 19 +++ src/languages/pl.ts | 19 +++ src/languages/pt-BR.ts | 19 +++ src/languages/zh-hans.ts | 19 +++ .../ModalStackNavigators/index.tsx | 2 + .../RELATIONS/WORKSPACE_TO_RHP.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 6 + src/libs/Navigation/types.ts | 6 + src/libs/actions/connections/Gusto.ts | 131 ++++++++++++++++- src/pages/workspace/hr/WorkspaceHRPage.tsx | 50 +++++++ .../hr/gusto/GustoApprovalModePage.tsx | 139 ++++++++++++++++++ .../hr/gusto/GustoFinalApproverPage.tsx | 61 ++++++++ 20 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 src/pages/workspace/hr/gusto/GustoApprovalModePage.tsx create mode 100644 src/pages/workspace/hr/gusto/GustoFinalApproverPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 4d3c977497dd..f54557ef968a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2446,6 +2446,24 @@ const ROUTES = { return `workspaces/${policyID}/hr` as const; }, }, + WORKSPACE_HR_GUSTO_APPROVAL_MODE: { + route: 'workspaces/:policyID/hr/gusto/approval-mode', + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_HR_GUSTO_APPROVAL_MODE route'); + } + 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 b9289a81f8ed..7294172ba98b 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -671,6 +671,8 @@ const SCREENS = { INITIAL: 'Workspace_Initial', 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/languages/de.ts b/src/languages/de.ts index 499db15acc88..96da7b9d39bc 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7030,6 +7030,25 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc title: 'Gusto', approvalMode: 'Genehmigungsmodus', finalApprover: 'Endgültige:r Genehmiger:in', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: 'Verbinden', connectionDescription: 'Verbinde Gusto, um Mitarbeitergenehmigungen mit deinem Workspace zu synchronisieren.', syncNow: 'Jetzt synchronisieren', diff --git a/src/languages/en.ts b/src/languages/en.ts index db0c869adeb2..057548d775d6 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6384,6 +6384,25 @@ const translations = { connectionDescription: 'Connect Gusto to keep employee approvals in sync with your workspace.', approvalMode: 'Approval mode', finalApprover: 'Final approver', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, }, }, export: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 5cf243f95099..8e7fff33651a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6249,6 +6249,25 @@ ${amount} para ${merchant} - ${date}`, connectionDescription: 'Conecta Gusto para mantener sincronizadas las aprobaciones de empleados con tu espacio de trabajo.', approvalMode: 'Modo de aprobación', finalApprover: 'Aprobador final', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, }, }, export: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 1dc26fa7ad1f..ca5f95c5f3d5 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7052,6 +7052,25 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e title: 'Gusto', approvalMode: 'Mode d’approbation', finalApprover: 'Approbateur final', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: 'Connect', connectionDescription: 'Connectez Gusto pour synchroniser les validations des employé·e·s avec votre espace de travail.', syncNow: 'Synchroniser maintenant', diff --git a/src/languages/it.ts b/src/languages/it.ts index 409eb16ce187..c18b71b3c347 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7015,6 +7015,25 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, title: 'Gusto', approvalMode: 'Modalità approvazione', finalApprover: 'Approvazione finale', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: 'Collega', connectionDescription: 'Collega Gusto per sincronizzare le approvazioni dei dipendenti con il tuo spazio di lavoro.', syncNow: 'Sincronizza ora', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index f85d3968da21..a4c9675cba90 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6938,6 +6938,25 @@ ${reportName} title: 'Gusto', approvalMode: '承認モード', finalApprover: '最終承認者', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: '接続', connectionDescription: 'Gusto を接続して、従業員の承認をワークスペースと同期させましょう。', syncNow: '今すぐ同期', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 43637941167e..827f6b369fc0 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6994,6 +6994,25 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, title: 'Gusto', approvalMode: 'Goedkeuringsmodus', finalApprover: 'Laatste fiatteur', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: 'Verbinden', connectionDescription: 'Verbind Gusto om goedkeuringen van werknemers gesynchroniseerd te houden met je workspace.', syncNow: 'Nu synchroniseren', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 53263beec1aa..4fa62749d66d 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6987,6 +6987,25 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, title: 'Gusto', approvalMode: 'Tryb zatwierdzania', finalApprover: 'Ostateczny zatwierdzający', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: 'Połącz', connectionDescription: 'Połącz Gusto, aby synchronizować akceptacje pracowników z Twoim miejscem pracy.', syncNow: 'Synchronizuj teraz', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 0e88a1d7dfe5..847ae686e89f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6993,6 +6993,25 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, title: 'Gusto', approvalMode: 'Modo de aprovação', finalApprover: 'Aprovador final', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: 'Conectar', connectionDescription: 'Conecte o Gusto para manter as aprovações de funcionários sincronizadas com seu workspace.', syncNow: 'Sincronizar agora', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 57a033f739b6..b8ed807f44ab 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -6820,6 +6820,25 @@ ${reportName} title: 'Gusto', approvalMode: '审批模式', finalApprover: '最终审批人', + notSet: 'Not set', + approvalModeDescription: 'Members and managers are set up to sync with Gusto.', + approvalModeWarningTitle: 'Change approval mode?', + approvalModeWarningPrompt: 'Changing the approval mode will sync Gusto again and may reset approval chains.', + approvalModeWarningConfirm: 'Change mode', + approvalModes: { + basic: { + label: 'Basic approval', + description: 'All users submit to a single person for processing and approval.', + }, + manager: { + label: 'Manager approval', + description: 'Employees submit reports to their direct manager configured in Gusto.', + }, + custom: { + label: 'Custom approval', + description: "I’ll manually setup approval workflows in Expensify.", + }, + }, connect: '连接', connectionDescription: '连接 Gusto,以在您的工作区中同步员工审批。', syncNow: '立即同步', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 080cbb419d16..3e06787b282d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -914,6 +914,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Security/LockAccount/LockAccountPage').default, [SCREENS.SETTINGS.LOCK.UNLOCK_ACCOUNT]: () => 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 13256acf6481..fa2a043dc5fe 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -180,6 +180,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.RULES_REIMBURSABLE_DEFAULT]: { path: ROUTES.RULES_REIMBURSABLE_DEFAULT.route, }, + [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 57c5dbd085ad..480ed6c7a737 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1455,6 +1455,12 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.RULES_REIMBURSABLE_DEFAULT]: { policyID: string; }; + [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 dab0d99eb924..b655f7367573 100644 --- a/src/libs/actions/connections/Gusto.ts +++ b/src/libs/actions/connections/Gusto.ts @@ -1,6 +1,13 @@ +import type {OnyxUpdate} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {ConnectPolicyToGustoParams} from '@libs/API/parameters'; -import {READ_COMMANDS} from '@libs/API/types'; +import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import * as API from '@libs/API'; import {getCommandURL} from '@libs/ApiUtils'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; function getGustoSetupLink(policyID: string) { const params: ConnectPolicyToGustoParams = {policyID}; @@ -11,4 +18,126 @@ function getGustoSetupLink(policyID: string) { return commandURL + new URLSearchParams(params).toString(); } +function updateGustoApprovalMode(policyID: string | undefined, approvalMode: ValueOf, currentApprovalMode?: ValueOf | null) { + if (!policyID) { + return; + } + + const previousApprovalMode = currentApprovalMode ?? null; + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + gusto: { + config: { + approvalMode, + pendingFields: {approvalMode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + errorFields: {approvalMode: null}, + }, + }, + }, + }, + }, + ]; + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + gusto: { + config: { + pendingFields: {approvalMode: null}, + errorFields: {approvalMode: null}, + }, + }, + }, + }, + }, + ]; + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + gusto: { + config: { + approvalMode: previousApprovalMode, + pendingFields: {approvalMode: null}, + errorFields: {approvalMode: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + }, + }, + }, + ]; + + API.write(WRITE_COMMANDS.UPDATE_GUSTO_APPROVAL_MODE, {policyID, approvalMode}, {optimisticData, successData, failureData}); +} + +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: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + }, + }, + }, + ]; + + API.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 445c5d210720..5a10cd700758 100644 --- a/src/pages/workspace/hr/WorkspaceHRPage.tsx +++ b/src/pages/workspace/hr/WorkspaceHRPage.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; import ActivityIndicator from '@components/ActivityIndicator'; import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; @@ -26,13 +27,16 @@ 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'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type WorkspaceHRPageProps = PlatformStackScreenProps; +type GustoApprovalMode = ValueOf; function WorkspaceHRPage({ route: { @@ -50,6 +54,7 @@ function WorkspaceHRPage({ const icons = useMemoizedLazyExpensifyIcons(['GustoSquare', 'Sync', 'Trashcan']); const illustrations = useMemoizedLazyIllustrations(['NewUser']); const gustoConnection = policy?.connections?.gusto; + const gustoConfig = gustoConnection?.config; const isConnected = isGustoConnected(policy); const isGustoSyncInProgress = connectionSyncProgress?.connectionName === CONST.POLICY.CONNECTIONS.NAME.GUSTO && isConnectionInProgress(connectionSyncProgress, policy); const stageInProgress = connectionSyncProgress?.stageInProgress; @@ -101,6 +106,29 @@ function WorkspaceHRPage({ ], [icons.Sync, icons.Trashcan, isOffline, policy, translate], ); + const getGustoApprovalModeLabel = (approvalMode?: GustoApprovalMode | null) => { + if (!approvalMode) { + return translate('workspace.hr.gusto.notSet'); + } + + switch (approvalMode) { + case CONST.GUSTO.APPROVAL_MODE.BASIC: + return translate('workspace.hr.gusto.approvalModes.basic.label'); + case CONST.GUSTO.APPROVAL_MODE.MANAGER: + return translate('workspace.hr.gusto.approvalModes.manager.label'); + case CONST.GUSTO.APPROVAL_MODE.CUSTOM: + return translate('workspace.hr.gusto.approvalModes.custom.label'); + default: + return translate('workspace.hr.gusto.notSet'); + } + }; + const getFinalApproverDisplayName = (finalApprover?: string | null) => { + if (!finalApprover) { + return translate('workspace.hr.gusto.notSet'); + } + + return getDisplayNameOrDefault(getPersonalDetailByEmail(finalApprover), finalApprover, false); + }; let gustoRowRightComponent; if (!isConnected) { @@ -178,6 +206,28 @@ function WorkspaceHRPage({ shouldShowRightComponent rightComponent={gustoRowRightComponent} /> + {isConnected && ( + <> + 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/GustoApprovalModePage.tsx b/src/pages/workspace/hr/gusto/GustoApprovalModePage.tsx new file mode 100644 index 000000000000..b28d63e2ef9f --- /dev/null +++ b/src/pages/workspace/hr/gusto/GustoApprovalModePage.tsx @@ -0,0 +1,139 @@ +import React, {useEffect, useState} from 'react'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import Button from '@components/Button'; +import ConfirmModal from '@components/ConfirmModal'; +import FixedFooter from '@components/FixedFooter'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem'; +import type {ListItem} from '@components/SelectionList/types'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; +import usePolicy from '@hooks/usePolicy'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {updateGustoApprovalMode} 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 type SCREENS from '@src/SCREENS'; + +type ApprovalMode = ValueOf; +type GustoApprovalModePageProps = PlatformStackScreenProps; +type ApprovalModeListItem = ListItem & { + value: ApprovalMode; +}; + +function GustoApprovalModePage({ + route: { + params: {policyID}, + }, +}: GustoApprovalModePageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {isBetaEnabled} = usePermissions(); + const policy = usePolicy(policyID); + const currentApprovalMode = policy?.connections?.gusto?.config?.approvalMode ?? undefined; + const [selectedApprovalMode, setSelectedApprovalMode] = useState(currentApprovalMode); + const [isWarningModalOpen, setIsWarningModalOpen] = useState(false); + const isSaveDisabled = !selectedApprovalMode || selectedApprovalMode === currentApprovalMode; + const approvalModeOptions: ApprovalModeListItem[] = [ + { + text: translate('workspace.hr.gusto.approvalModes.basic.label'), + alternateText: translate('workspace.hr.gusto.approvalModes.basic.description'), + keyForList: CONST.GUSTO.APPROVAL_MODE.BASIC, + value: CONST.GUSTO.APPROVAL_MODE.BASIC, + isSelected: selectedApprovalMode === CONST.GUSTO.APPROVAL_MODE.BASIC, + }, + { + text: translate('workspace.hr.gusto.approvalModes.manager.label'), + alternateText: translate('workspace.hr.gusto.approvalModes.manager.description'), + keyForList: CONST.GUSTO.APPROVAL_MODE.MANAGER, + value: CONST.GUSTO.APPROVAL_MODE.MANAGER, + isSelected: selectedApprovalMode === CONST.GUSTO.APPROVAL_MODE.MANAGER, + }, + { + text: translate('workspace.hr.gusto.approvalModes.custom.label'), + alternateText: translate('workspace.hr.gusto.approvalModes.custom.description'), + keyForList: CONST.GUSTO.APPROVAL_MODE.CUSTOM, + value: CONST.GUSTO.APPROVAL_MODE.CUSTOM, + isSelected: selectedApprovalMode === CONST.GUSTO.APPROVAL_MODE.CUSTOM, + }, + ]; + const selectedApprovalModeKey = approvalModeOptions.find((approvalMode) => approvalMode.isSelected)?.keyForList; + + useEffect(() => { + setSelectedApprovalMode(currentApprovalMode); + }, [currentApprovalMode]); + + const selectApprovalMode = (approvalMode: ApprovalMode) => { + setSelectedApprovalMode(approvalMode); + }; + + const saveApprovalMode = () => { + if (!selectedApprovalMode) { + return; + } + + updateGustoApprovalMode(policyID, selectedApprovalMode, currentApprovalMode); + setIsWarningModalOpen(false); + Navigation.goBack(); + }; + + return ( + + + Navigation.goBack()} + /> + + {translate('workspace.hr.gusto.approvalModeDescription')} + selectApprovalMode(option.value)} + shouldSingleExecuteRowSelect + initiallyFocusedItemKey={selectedApprovalModeKey} + alternateNumberOfSupportedLines={3} + showScrollIndicator={false} + /> + +