diff --git a/src/CONST/index.ts b/src/CONST/index.ts index d41a326a1db3..924145528146 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1212,6 +1212,7 @@ const CONST = { CUSTOM_REPORT_NAME_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/spending-insights/Export-Expenses-And-Reports#formulas', CONFIGURE_REIMBURSEMENT_SETTINGS_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/workspaces/Configure-Reimbursement-Settings', CONFIGURE_EXPENSE_REPORT_RULES_HELP_URL: 'https://help.expensify.com/articles/new-expensify/workspaces/Set-up-rules#configure-expense-report-rules', + CONFIGURE_APPROVAL_WORKFLOWS_HELP_URL: 'https://help.expensify.com/articles/new-expensify/workspaces/Configure-approval-workflows', SELECT_WORKFLOWS_HELP_URL: 'https://help.expensify.com/articles/new-expensify/workspaces/Set-up-workflows#select-workflows', COPILOT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Add-or-Act-As-a-Copilot', BULK_UPLOAD_HELP_URL: 'https://help.expensify.com/articles/new-expensify/reports-and-expenses/Create-an-Expense#option-4-bulk-upload-receipts-desktop-only', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 4d3c977497dd..14606cf20714 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2446,6 +2446,15 @@ 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_TAGS: { route: 'workspaces/:policyID/tags', getRoute: (policyID: string | undefined) => { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index b9289a81f8ed..a06374758505 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -671,6 +671,7 @@ const SCREENS = { INITIAL: 'Workspace_Initial', PROFILE: 'Workspace_Overview', HR: 'Workspace_HR', + HR_GUSTO_APPROVAL_MODE: 'Workspace_HR_Gusto_Approval_Mode', 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..3f0f6f16292a 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7030,6 +7030,26 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc title: 'Gusto', approvalMode: 'Genehmigungsmodus', finalApprover: 'Endgültige:r Genehmiger:in', + notSet: 'Nicht festgelegt', + approvalModeDescription: 'Mitglieder und Vorgesetzte sind für die Synchronisierung mit Gusto eingerichtet.', + approvalModeWarningTitle: 'Genehmigungsmodus ändern?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `Möchtest du den Genehmigungsmodus für diesen Arbeitsbereich wirklich ändern? Weitere Informationen zu den verschiedenen Gusto-gestützten Arbeitsablaufmodi findest du auf unserer Hilfeseite.`, + approvalModeWarningConfirm: 'Genehmigungsmodus ändern', + approvalModes: { + basic: { + label: 'Einfache Genehmigung', + description: 'Alle Benutzer reichen bei einer einzelnen Person zur Bearbeitung und Genehmigung ein.', + }, + manager: { + label: 'Genehmigung durch Vorgesetzte', + description: 'Mitarbeitende reichen Berichte bei ihrem in Gusto konfigurierten direkten Vorgesetzten ein.', + }, + custom: { + label: 'Benutzerdefinierte Genehmigung', + description: 'Ich richte Genehmigungsworkflows manuell in Expensify ein.', + }, + }, 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..c26c3fd0c5b1 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6384,6 +6384,26 @@ 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: (helpSiteURL: string) => + `Are you sure you would like to change the approval mode for this workspace? Learn more about the different Gusto-enabled workflow modes in our help site.`, + approvalModeWarningConfirm: 'Change approval 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..7a66d2ac87ea 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6249,6 +6249,26 @@ ${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: 'No configurado', + approvalModeDescription: 'Los miembros y gerentes están configurados para sincronizarse con Gusto.', + approvalModeWarningTitle: '¿Cambiar modo de aprobación?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `¿Seguro que quieres cambiar el modo de aprobación de este espacio de trabajo? Obtén más información sobre los diferentes modos de flujo de trabajo con Gusto en nuestro sitio de ayuda.`, + approvalModeWarningConfirm: 'Cambiar modo de aprobación', + approvalModes: { + basic: { + label: 'Aprobación básica', + description: 'Todos los usuarios envían a una sola persona para su procesamiento y aprobación.', + }, + manager: { + label: 'Aprobación del gerente', + description: 'Los empleados envían informes a su gerente directo configurado en Gusto.', + }, + custom: { + label: 'Aprobación personalizada', + description: 'Configuraré manualmente los flujos de aprobación en Expensify.', + }, + }, }, }, export: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 1dc26fa7ad1f..be61d34eec20 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7052,6 +7052,26 @@ 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: 'Non défini', + approvalModeDescription: 'Les membres et les responsables sont configurés pour se synchroniser avec Gusto.', + approvalModeWarningTitle: 'Modifier le mode d’approbation ?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `Voulez-vous vraiment modifier le mode d’approbation de cet espace de travail ? En savoir plus sur les différents modes de flux de travail compatibles avec Gusto sur notre site d’aide.`, + approvalModeWarningConfirm: 'Modifier le mode d’approbation', + approvalModes: { + basic: { + label: 'Approbation simple', + description: 'Tous les utilisateurs soumettent leurs rapports à une seule personne pour traitement et approbation.', + }, + manager: { + label: 'Approbation par responsable', + description: 'Les employés soumettent leurs rapports à leur responsable direct configuré dans Gusto.', + }, + custom: { + label: 'Approbation personnalisée', + description: 'Je configurerai manuellement les flux de travail d’approbation dans 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..7857c2a32b56 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7015,6 +7015,26 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, title: 'Gusto', approvalMode: 'Modalità approvazione', finalApprover: 'Approvazione finale', + notSet: 'Non impostato', + approvalModeDescription: 'Membri e responsabili sono configurati per sincronizzarsi con Gusto.', + approvalModeWarningTitle: 'Cambiare modalità di approvazione?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `Vuoi davvero cambiare la modalità di approvazione per questo spazio di lavoro? Scopri di più sulle diverse modalità di flusso di lavoro abilitate da Gusto nel nostro sito di assistenza.`, + approvalModeWarningConfirm: 'Cambia modalità di approvazione', + approvalModes: { + basic: { + label: 'Approvazione di base', + description: 'Tutti gli utenti inviano a una sola persona per l’elaborazione e l’approvazione.', + }, + manager: { + label: 'Approvazione del manager', + description: 'I dipendenti inviano i report al proprio manager diretto configurato in Gusto.', + }, + custom: { + label: 'Approvazione personalizzata', + description: 'Configurerò manualmente i flussi di lavoro di approvazione 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..7afbf7d78fac 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6938,6 +6938,26 @@ ${reportName} title: 'Gusto', approvalMode: '承認モード', finalApprover: '最終承認者', + notSet: '未設定', + approvalModeDescription: 'メンバーとマネージャーは Gusto と同期するように設定されています。', + approvalModeWarningTitle: '承認モードを変更しますか?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `このワークスペースの承認モードを変更してもよろしいですか? Gusto 対応の各ワークフローモードについて詳しくは、ヘルプサイトをご覧ください。`, + approvalModeWarningConfirm: '承認モードを変更', + approvalModes: { + basic: { + label: '基本承認', + description: 'すべてのユーザーは、処理と承認のために1人の担当者に提出します。', + }, + manager: { + label: 'マネージャー承認', + description: '従業員は、Gusto で設定された直属のマネージャーにレポートを提出します。', + }, + custom: { + label: 'カスタム承認', + description: 'Expensify で承認ワークフローを手動で設定します。', + }, + }, connect: '接続', connectionDescription: 'Gusto を接続して、従業員の承認をワークスペースと同期させましょう。', syncNow: '今すぐ同期', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 43637941167e..d3655cb1d7cd 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6994,6 +6994,26 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, title: 'Gusto', approvalMode: 'Goedkeuringsmodus', finalApprover: 'Laatste fiatteur', + notSet: 'Niet ingesteld', + approvalModeDescription: 'Leden en managers zijn ingesteld om met Gusto te synchroniseren.', + approvalModeWarningTitle: 'Goedkeuringsmodus wijzigen?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `Weet je zeker dat je de goedkeuringsmodus voor deze werkruimte wilt wijzigen? Lees meer over de verschillende Gusto-werkstroommodi op onze helpsite.`, + approvalModeWarningConfirm: 'Goedkeuringsmodus wijzigen', + approvalModes: { + basic: { + label: 'Basisgoedkeuring', + description: 'Alle gebruikers dienen in bij één persoon voor verwerking en goedkeuring.', + }, + manager: { + label: 'Goedkeuring door manager', + description: 'Medewerkers dienen rapporten in bij hun directe manager die in Gusto is geconfigureerd.', + }, + custom: { + label: 'Aangepaste goedkeuring', + description: 'Ik stel goedkeuringswerkstromen handmatig in Expensify in.', + }, + }, 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..87b4534970a4 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6987,6 +6987,26 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, title: 'Gusto', approvalMode: 'Tryb zatwierdzania', finalApprover: 'Ostateczny zatwierdzający', + notSet: 'Nie ustawiono', + approvalModeDescription: 'Członkowie i menedżerowie są skonfigurowani do synchronizacji z Gusto.', + approvalModeWarningTitle: 'Zmienić tryb zatwierdzania?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `Czy na pewno chcesz zmienić tryb zatwierdzania dla tej przestrzeni roboczej? Dowiedz się więcej o różnych trybach przepływu pracy obsługiwanych przez Gusto w naszej witrynie pomocy.`, + approvalModeWarningConfirm: 'Zmień tryb zatwierdzania', + approvalModes: { + basic: { + label: 'Podstawowe zatwierdzanie', + description: 'Wszyscy użytkownicy przesyłają do jednej osoby do przetworzenia i zatwierdzenia.', + }, + manager: { + label: 'Zatwierdzanie przez menedżera', + description: 'Pracownicy przesyłają raporty do swojego bezpośredniego menedżera skonfigurowanego w Gusto.', + }, + custom: { + label: 'Niestandardowe zatwierdzanie', + description: 'Ręcznie skonfiguruję procesy zatwierdzania w 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..29d884aefdb4 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6993,6 +6993,26 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, title: 'Gusto', approvalMode: 'Modo de aprovação', finalApprover: 'Aprovador final', + notSet: 'Não definido', + approvalModeDescription: 'Membros e gerentes estão configurados para sincronizar com o Gusto.', + approvalModeWarningTitle: 'Alterar modo de aprovação?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `Tem certeza de que deseja alterar o modo de aprovação deste espaço de trabalho? Saiba mais sobre os diferentes modos de fluxo de trabalho habilitados pelo Gusto em nosso site de ajuda.`, + approvalModeWarningConfirm: 'Alterar modo de aprovação', + approvalModes: { + basic: { + label: 'Aprovação básica', + description: 'Todos os usuários enviam para uma única pessoa para processamento e aprovação.', + }, + manager: { + label: 'Aprovação do gerente', + description: 'Os funcionários enviam relatórios para o gerente direto configurado no Gusto.', + }, + custom: { + label: 'Aprovação personalizada', + description: 'Vou configurar manualmente os fluxos de trabalho de aprovação no 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..2b64aa7e085a 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -6820,6 +6820,26 @@ ${reportName} title: 'Gusto', approvalMode: '审批模式', finalApprover: '最终审批人', + notSet: '未设置', + approvalModeDescription: '成员和经理已设置为与 Gusto 同步。', + approvalModeWarningTitle: '更改审批模式?', + approvalModeWarningPrompt: (helpSiteURL: string) => + `你确定要更改此工作区的审批模式吗?请在我们的帮助网站中了解更多关于不同 Gusto 启用工作流模式的信息。`, + approvalModeWarningConfirm: '更改审批模式', + approvalModes: { + basic: { + label: '基本审批', + description: '所有用户都提交给同一个人进行处理和审批。', + }, + manager: { + label: '经理审批', + description: '员工将报告提交给 Gusto 中配置的直属经理。', + }, + custom: { + label: '自定义审批', + description: '我将在 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..f4e6191b5a10 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -914,6 +914,7 @@ 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.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..f11d61afc7f6 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.RULES_CUSTOM]: { path: ROUTES.RULES_CUSTOM.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 57c5dbd085ad..dddf2cde4f73 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1455,6 +1455,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.RULES_REIMBURSABLE_DEFAULT]: { policyID: string; }; + [SCREENS.WORKSPACE.HR_GUSTO_APPROVAL_MODE]: { + 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..1c434041ec81 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 {write} from '@libs/API'; import type {ConnectPolicyToGustoParams} from '@libs/API/parameters'; -import {READ_COMMANDS} from '@libs/API/types'; +import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import {getCommandURL} from '@libs/ApiUtils'; +import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; function getGustoSetupLink(policyID: string) { const params: ConnectPolicyToGustoParams = {policyID}; @@ -11,4 +18,70 @@ 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: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + }, + }, + }, + ]; + + write(WRITE_COMMANDS.UPDATE_GUSTO_APPROVAL_MODE, {policyID, approvalMode}, {optimisticData, successData, failureData}); +} + +export {updateGustoApprovalMode}; + export default getGustoSetupLink; diff --git a/src/pages/workspace/hr/WorkspaceHRPage.tsx b/src/pages/workspace/hr/WorkspaceHRPage.tsx index 445c5d210720..d83eb52782ac 100644 --- a/src/pages/workspace/hr/WorkspaceHRPage.tsx +++ b/src/pages/workspace/hr/WorkspaceHRPage.tsx @@ -1,11 +1,14 @@ 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'; import ConnectToGustoFlow from '@components/ConnectToGustoFlow'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; @@ -30,9 +33,11 @@ import {getIntegrationLastSuccessfulDate, isGustoConnected} from '@libs/PolicyUt 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 +55,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,7 +107,22 @@ 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'); + } + }; let gustoRowRightComponent; if (!isConnected) { gustoRowRightComponent = ( @@ -178,6 +199,18 @@ function WorkspaceHRPage({ shouldShowRightComponent rightComponent={gustoRowRightComponent} /> + {isConnected && ( + + Navigation.navigate(ROUTES.WORKSPACE_HR_GUSTO_APPROVAL_MODE.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..a2b50ffd26a6 --- /dev/null +++ b/src/pages/workspace/hr/gusto/GustoApprovalModePage.tsx @@ -0,0 +1,137 @@ +import React, {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 RenderHTML from '@components/RenderHTML'; +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 [draftApprovalMode, setDraftApprovalMode] = useState(); + const [isWarningModalOpen, setIsWarningModalOpen] = useState(false); + const selectedApprovalMode = draftApprovalMode ?? currentApprovalMode; + const isSaveDisabled = !draftApprovalMode || draftApprovalMode === 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; + + const selectApprovalMode = (approvalMode: ApprovalMode) => { + setDraftApprovalMode(approvalMode); + }; + + const saveApprovalMode = () => { + if (!draftApprovalMode) { + return; + } + + updateGustoApprovalMode(policyID, draftApprovalMode, currentApprovalMode); + setIsWarningModalOpen(false); + Navigation.goBack(); + }; + + return ( + + + Navigation.goBack()} + /> + + {translate('workspace.hr.gusto.approvalModeDescription')} + selectApprovalMode(option.value)} + shouldSingleExecuteRowSelect + initiallyFocusedItemKey={selectedApprovalModeKey} + alternateNumberOfSupportedLines={3} + showScrollIndicator={false} + /> + +