From d51a8da695af3567e5712e43a8447536d250bcd1 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 12 Aug 2024 13:48:47 +0200 Subject: [PATCH 01/28] Start working on edit screen --- src/libs/Permissions.ts | 2 +- .../approvals/ApprovalWorkflowEditor.tsx | 24 ++++++- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 65 ++++++++++++++++++- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index f652c2a423ec..41ba85e1b10e 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.ALL); + return !!betas?.includes(CONST.BETAS.ALL) || true; } function canUseDefaultRooms(betas: OnyxEntry): boolean { diff --git a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx index d8a6c5d04410..fd8dd6920459 100644 --- a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx +++ b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx @@ -3,10 +3,13 @@ import React, {forwardRef, useCallback} from 'react'; import {View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {ScrollView as ScrollViewRN} from 'react-native'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; @@ -15,12 +18,19 @@ import type {ApprovalWorkflowOnyx} from '@src/types/onyx'; import type {Approver} from '@src/types/onyx/ApprovalWorkflow'; type ApprovalWorkflowEditorProps = { + /** The approval workflow to display */ approvalWorkflow: ApprovalWorkflowOnyx; + + /** Function to remove the approval workflow */ + removeApprovalWorkflow?: () => void; + + /** The policy ID */ policyID: string; }; -function ApprovalWorkflowEditor({approvalWorkflow, policyID}: ApprovalWorkflowEditorProps, ref: ForwardedRef) { +function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, policyID}: ApprovalWorkflowEditorProps, ref: ForwardedRef) { const styles = useThemeStyles(); + const theme = useTheme(); const {translate, toLocaleOrdinal} = useLocalize(); const approverDescription = useCallback( @@ -73,7 +83,7 @@ function ApprovalWorkflowEditor({approvalWorkflow, policyID}: ApprovalWorkflowEd ref={ref} > - {translate('workflowsCreateApprovalsPage.header')} + {approvalWorkflow.flow === 'create' && {translate('workflowsCreateApprovalsPage.header')}} + + {removeApprovalWorkflow && ( + removeApprovalWorkflow()} + /> + )} ); diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 8b55831b276c..d367f09ea6b3 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -1,27 +1,60 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useCallback, useRef, useState} from 'react'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import ConfirmModal from '@components/ConfirmModal'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import * as Workflow from '@userActions/Workflow'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import ApprovalWorkflowEditor from './ApprovalWorkflowEditor'; type WorkspaceWorkflowsApprovalsEditPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true, route}: WorkspaceWorkflowsApprovalsEditPageProps) { + const styles = useThemeStyles(); const {translate} = useLocalize(); + const [approvalWorkflow, approvalWorkflowMetadata] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const formRef = useRef(null); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy); + const updateApprovalWorkflow = useCallback(() => { + if (!approvalWorkflow) { + return; + } + + if (!isEmptyObject(Workflow.validateApprovalWorkflow(approvalWorkflow))) { + return; + } + + // Workflow.updateApprovalWorkflow(route.params.policyID, {...approvalWorkflow, approvers: approvalWorkflow.approvers as Approver[]}); + Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + }, [approvalWorkflow, route.params.policyID]); + + const removeApprovalWorkflow = useCallback(() => { + // Workflow.removeApprovalWorkflow(route.params.policyID); + Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + }, [route.params.policyID]); + return ( + {approvalWorkflowMetadata.status === 'loading' && } + {approvalWorkflow && ( + setIsDeleteModalVisible(true)} + policyID={route.params.policyID} + ref={formRef} + /> + )} + + { + formRef.current?.scrollTo({y: 0, animated: true}); + }} + isLoading={approvalWorkflow?.isLoading} + buttonText={translate('common.save')} + containerStyles={[styles.mb5, styles.mh5]} + /> + setIsDeleteModalVisible(false)} + prompt={translate('workspace.taxes.deleteTaxConfirmation')} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> ); From 3ed8226cb5cb0625429e69185c91dd26576d5e11 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 12 Aug 2024 17:28:17 +0200 Subject: [PATCH 02/28] Bring back old canUseAllBetas --- src/libs/Permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 41ba85e1b10e..f652c2a423ec 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.ALL) || true; + return !!betas?.includes(CONST.BETAS.ALL); } function canUseDefaultRooms(betas: OnyxEntry): boolean { From 25d6df70918df8b689d7fb28c83f927d6c2309c7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 12 Aug 2024 18:47:12 +0200 Subject: [PATCH 03/28] Improve edit logic --- src/components/ApprovalWorkflowSection.tsx | 19 +++---- src/libs/actions/Workflow.ts | 3 +- .../workflows/WorkspaceWorkflowsPage.tsx | 17 +++++- .../approvals/ApprovalWorkflowEditor.tsx | 52 +++++++++++++------ .../WorkspaceWorkflowsApprovalsCreatePage.tsx | 1 + .../WorkspaceWorkflowsApprovalsEditPage.tsx | 1 + 6 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 49e509ce533e..17cadd3826ae 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -4,8 +4,6 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import Navigation from '@libs/Navigation/Navigation'; -import ROUTES from '@src/ROUTES'; import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; @@ -17,19 +15,16 @@ type ApprovalWorkflowSectionProps = { /** Single workflow displayed in this component */ approvalWorkflow: ApprovalWorkflow; - /** ID of the policy */ - policyID: string; + /** Function to call when the user clicks on section */ + onPress: () => void; }; -function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowSectionProps) { +function ApprovalWorkflowSection({approvalWorkflow, onPress}: ApprovalWorkflowSectionProps) { const styles = useThemeStyles(); const theme = useTheme(); const {translate, toLocaleOrdinal} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); - const openApprovalsEdit = useCallback( - () => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(policyID, approvalWorkflow.approvers[0].email)), - [approvalWorkflow.approvers, policyID], - ); + const approverTitle = useCallback( (index: number) => approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`, @@ -40,7 +35,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowS @@ -70,7 +65,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowS iconHeight={20} iconWidth={20} iconFill={theme.icon} - onPress={openApprovalsEdit} + onPress={onPress} shouldRemoveBackground /> @@ -88,7 +83,7 @@ function ApprovalWorkflowSection({approvalWorkflow, policyID}: ApprovalWorkflowS iconHeight={20} iconWidth={20} iconFill={theme.icon} - onPress={openApprovalsEdit} + onPress={onPress} shouldRemoveBackground /> diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index 6e614831234a..1bd74a8efcde 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -162,8 +162,9 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList: previousEmployeeList, mode: CONST.APPROVAL_WORKFLOW.MODE.REMOVE}); const updatedEmployeeList = {...previousEmployeeList, ...updatedEmployees}; + const defaultApprover = policy.approver ?? policy.owner; // If there is more than one workflow, we need to keep the advanced approval mode (first workflow is the default) - const hasMoreThanOneWorkflow = Object.values(updatedEmployeeList).some((employee) => !!employee.submitsTo && employee.submitsTo !== policy.approver); + const hasMoreThanOneWorkflow = Object.values(updatedEmployeeList).some((employee) => !!employee.submitsTo && employee.submitsTo !== defaultApprover); const optimisticData: OnyxUpdate[] = [ { diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 55882e7885b1..98455c83def5 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -40,6 +40,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Beta} from '@src/types/onyx'; +import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import ToggleSettingOptionRow from './ToggleSettingsOptionRow'; import type {ToggleSettingOptionRowProps} from './ToggleSettingsOptionRow'; import {getAutoReportingFrequencyDisplayNames} from './WorkspaceAutoReportingFrequencyPage'; @@ -115,6 +116,19 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(route.params.policyID)); }, [approvalWorkflows, policy, route.params.policyID]); + const editApprovalAction = useCallback( + (workflow: ApprovalWorkflow) => { + Workflow.setApprovalWorkflow({ + ...workflow, + availableMembers: approvalWorkflows.at(0)?.members ?? [], + flow: CONST.APPROVAL_WORKFLOW.FLOW.EDIT, + isLoading: false, + }); + Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, workflow.approvers[0].email)); + }, + [approvalWorkflows, route.params.policyID], + ); + const optionItems: ToggleSettingOptionRowProps[] = useMemo(() => { const {accountNumber, addressName, bankName, bankAccountID} = policy?.achAccount ?? {}; const shouldShowBankAccount = !!bankAccountID && policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES; @@ -175,7 +189,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr // eslint-disable-next-line react/no-array-index-key key={`workflow-${index}`} approvalWorkflow={workflow} - policyID={route.params.policyID} + onPress={() => editApprovalAction(workflow)} /> ))} ( diff --git a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx index e3ef6a2716c8..8f6ca2c22b25 100644 --- a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx +++ b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx @@ -3,6 +3,7 @@ import React, {forwardRef, useCallback} from 'react'; import {View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {ScrollView as ScrollViewRN} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -12,9 +13,10 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import type {ApprovalWorkflowOnyx} from '@src/types/onyx'; +import type {ApprovalWorkflowOnyx, Policy} from '@src/types/onyx'; import type {Approver} from '@src/types/onyx/ApprovalWorkflow'; type ApprovalWorkflowEditorProps = { @@ -24,11 +26,14 @@ type ApprovalWorkflowEditorProps = { /** Function to remove the approval workflow */ removeApprovalWorkflow?: () => void; + /** The policy for the current route */ + policy: OnyxEntry; + /** The policy ID */ policyID: string; }; -function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, policyID}: ApprovalWorkflowEditorProps, ref: ForwardedRef) { +function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, policy, policyID}: ApprovalWorkflowEditorProps, ref: ForwardedRef) { const styles = useThemeStyles(); const theme = useTheme(); const {translate, toLocaleOrdinal} = useLocalize(); @@ -77,6 +82,28 @@ function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, polic [approvalWorkflow.approvers, translate], ); + const editMembers = useCallback(() => { + const backTo = approvalWorkflow.flow === CONST.APPROVAL_WORKFLOW.FLOW.CREATE ? ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID) : undefined; + Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(policyID, backTo)); + }, [approvalWorkflow.flow, policyID]); + + const editApprover = useCallback( + (approverIndex: number) => { + const backTo = approvalWorkflow.flow === CONST.APPROVAL_WORKFLOW.FLOW.CREATE ? ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID) : undefined; + Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(policyID, approverIndex, backTo)); + }, + [approvalWorkflow.flow, policyID], + ); + + // User should be allowed to add additional approver only if they upgraded to Control Plan, otherwise redirected to the Upgrade Page + const addAdditionalApprover = useCallback(() => { + if (!PolicyUtils.isControlPolicy(policy)) { + Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.approvals.alias, ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID))); + return; + } + Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(policyID, approvalWorkflow.approvers.length, ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID))); + }, [approvalWorkflow.approvers.length, policy, policyID]); + return ( Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(policyID, ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID)))} - shouldShowRightIcon + onPress={editMembers} wrapperStyle={[styles.sectionMenuItemTopDescription]} errorText={approvalWorkflow?.errors?.members ? translate(approvalWorkflow.errors.members) : undefined} brickRoadIndicator={approvalWorkflow?.errors?.members ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + shouldShowRightIcon={!approvalWorkflow.isDefault} + interactive={!approvalWorkflow.isDefault} /> {approvalWorkflow.approvers.map((approver, approverIndex) => { @@ -109,11 +137,7 @@ function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, polic wrapperStyle={styles.sectionMenuItemTopDescription} description={approverDescription(approverIndex)} descriptionTextStyle={!!approver?.displayName && styles.textLabelSupportingNormal} - onPress={() => - Navigation.navigate( - ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(policyID, approverIndex, ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID)), - ) - } + onPress={() => editApprover(approverIndex)} shouldShowRightIcon hintText={hintText} shouldParseHintText @@ -126,24 +150,20 @@ function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, polic - Navigation.navigate( - ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(policyID, approvalWorkflow.approvers.length, ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID)), - ) - } + onPress={addAdditionalApprover} shouldShowRightIcon wrapperStyle={styles.sectionMenuItemTopDescription} errorText={approvalWorkflow?.errors?.additionalApprover ? translate(approvalWorkflow.errors.additionalApprover) : undefined} brickRoadIndicator={approvalWorkflow?.errors?.additionalApprover ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} /> - {removeApprovalWorkflow && ( + {removeApprovalWorkflow && !approvalWorkflow.isDefault && ( removeApprovalWorkflow()} + onPress={removeApprovalWorkflow} /> )} diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx index 6c95b30a8181..2a624bf4626a 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx @@ -72,6 +72,7 @@ function WorkspaceWorkflowsApprovalsCreatePage({policy, isLoadingReportData = tr {approvalWorkflow && ( diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index d367f09ea6b3..f34ded1d9c35 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -79,6 +79,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true setIsDeleteModalVisible(true)} + policy={policy} policyID={route.params.policyID} ref={formRef} /> From aca6ce4a713191625237a8c70e641eb23e2f3e7d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 09:35:26 +0200 Subject: [PATCH 04/28] Rename flow to action --- src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index fb798c61880b..11fd5edd857c 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -121,7 +121,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr Workflow.setApprovalWorkflow({ ...workflow, availableMembers: approvalWorkflows.at(0)?.members ?? [], - flow: CONST.APPROVAL_WORKFLOW.FLOW.EDIT, + action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, isLoading: false, }); Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, workflow.approvers[0].email)); From 92ddb88bd44540ab033924e51414a6096c1eafb0 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 10:41:04 +0200 Subject: [PATCH 05/28] Add missing translations --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index a8264a24ab5b..12b72cdd10be 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1324,6 +1324,8 @@ export default { }, workflowsEditApprovalsPage: { title: 'Edit approval workflow', + deleteTitle: 'Delete approval workflow', + deletePrompt: 'Are you sure you want to delete this approval workflow? All members will subsequently follow the default workflow.', }, workflowsExpensesFromPage: { title: 'Expenses from', diff --git a/src/languages/es.ts b/src/languages/es.ts index 4386329d2d6d..32c921e1f970 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1333,6 +1333,8 @@ export default { }, workflowsEditApprovalsPage: { title: 'Edicion flujo de aprobación', + deleteTitle: 'Eliminar flujo de trabajo de aprobación', + deletePrompt: '¿Estás seguro de que quieres eliminar este flujo de trabajo de aprobación? Todos los miembros pasarán a usar el flujo de trabajo predeterminado.', }, workflowsExpensesFromPage: { title: 'Gastos de', From dc013da1b6d2dc3777b9706414ebb21160845fae Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 10:41:54 +0200 Subject: [PATCH 06/28] Fix the logic behind updating employees --- src/libs/WorkflowUtils.ts | 11 +++-------- src/libs/actions/Workflow.ts | 6 +++--- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/libs/WorkflowUtils.ts b/src/libs/WorkflowUtils.ts index 71348368e0d5..c145b18aff79 100644 --- a/src/libs/WorkflowUtils.ts +++ b/src/libs/WorkflowUtils.ts @@ -152,11 +152,6 @@ type ConvertApprovalWorkflowToPolicyEmployeesParams = { */ approvalWorkflow: ApprovalWorkflow; - /** - * Current list of employees in the policy - */ - employeeList: PolicyEmployeeList; - /** * Mode to use when converting the approval workflow */ @@ -164,7 +159,7 @@ type ConvertApprovalWorkflowToPolicyEmployeesParams = { }; /** Convert an approval workflow to a list of policy employees */ -function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList, type}: ConvertApprovalWorkflowToPolicyEmployeesParams): PolicyEmployeeList { +function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type}: ConvertApprovalWorkflowToPolicyEmployeesParams): PolicyEmployeeList { const updatedEmployeeList: PolicyEmployeeList = {}; const firstApprover = approvalWorkflow.approvers.at(0); @@ -175,14 +170,14 @@ function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeLis approvalWorkflow.approvers.forEach((approver, index) => { const nextApprover = approvalWorkflow.approvers.at(index + 1); updatedEmployeeList[approver.email] = { - ...employeeList[approver.email], + email: approver.email, forwardsTo: type === CONST.APPROVAL_WORKFLOW.TYPE.REMOVE ? '' : nextApprover?.email ?? '', }; }); approvalWorkflow.members.forEach(({email}) => { updatedEmployeeList[email] = { - ...(updatedEmployeeList[email] ? updatedEmployeeList[email] : employeeList[email]), + ...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}), submitsTo: type === CONST.APPROVAL_WORKFLOW.TYPE.REMOVE ? '' : firstApprover.email ?? '', }; }); diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index 6e69253edb90..35fea58a9551 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -53,7 +53,7 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork const previousEmployeeList = {...policy.employeeList}; const previousApprovalMode = policy.approvalMode; - const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList: previousEmployeeList, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE}); + const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE}); const optimisticData: OnyxUpdate[] = [ { @@ -118,7 +118,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork } const previousEmployeeList = {...policy.employeeList}; - const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList: previousEmployeeList, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE}); + const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE}); const optimisticData: OnyxUpdate[] = [ { @@ -181,7 +181,7 @@ function removeApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork } const previousEmployeeList = {...policy.employeeList}; - const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList: previousEmployeeList, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE}); + const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE}); const updatedEmployeeList = {...previousEmployeeList, ...updatedEmployees}; const defaultApprover = policy.approver ?? policy.owner; From bd6e8c00a6c2642f8d1b292a90448ecc29a6e556 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 10:42:05 +0200 Subject: [PATCH 07/28] Use new names for variables --- .../workflows/approvals/ApprovalWorkflowEditor.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx index 0f4b2a39dbb6..7e039b5478b1 100644 --- a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx +++ b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx @@ -83,16 +83,16 @@ function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, polic ); const editMembers = useCallback(() => { - const backTo = approvalWorkflow.flow === CONST.APPROVAL_WORKFLOW.FLOW.CREATE ? ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID) : undefined; + const backTo = approvalWorkflow.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE ? ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID) : undefined; Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(policyID, backTo)); - }, [approvalWorkflow.flow, policyID]); + }, [approvalWorkflow.action, policyID]); const editApprover = useCallback( (approverIndex: number) => { - const backTo = approvalWorkflow.flow === CONST.APPROVAL_WORKFLOW.FLOW.CREATE ? ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID) : undefined; + const backTo = approvalWorkflow.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE ? ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID) : undefined; Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(policyID, approverIndex, backTo)); }, - [approvalWorkflow.flow, policyID], + [approvalWorkflow.action, policyID], ); // User should be allowed to add additional approver only if they upgraded to Control Plan, otherwise redirected to the Upgrade Page @@ -110,7 +110,9 @@ function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, polic ref={ref} > - {approvalWorkflow.flow === 'create' && {translate('workflowsCreateApprovalsPage.header')}} + {approvalWorkflow.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && ( + {translate('workflowsCreateApprovalsPage.header')} + )} Date: Tue, 13 Aug 2024 10:42:20 +0200 Subject: [PATCH 08/28] Start testing removal flow --- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index f34ded1d9c35..6e42a24fb1f9 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -22,6 +22,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {Approver} from '@src/types/onyx/ApprovalWorkflow'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ApprovalWorkflowEditor from './ApprovalWorkflowEditor'; @@ -51,9 +52,13 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true }, [approvalWorkflow, route.params.policyID]); const removeApprovalWorkflow = useCallback(() => { - // Workflow.removeApprovalWorkflow(route.params.policyID); + if (!approvalWorkflow) { + return; + } + + Workflow.removeApprovalWorkflow(route.params.policyID, {...approvalWorkflow, approvers: approvalWorkflow.approvers as Approver[]}); Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); - }, [route.params.policyID]); + }, [approvalWorkflow, route.params.policyID]); return ( setIsDeleteModalVisible(false)} - prompt={translate('workspace.taxes.deleteTaxConfirmation')} + prompt={translate('workflowsEditApprovalsPage.deletePrompt')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger From 1ef7ad2566fd1905f462b538bb5b3accb5e44e70 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 12:26:34 +0200 Subject: [PATCH 09/28] Change what validation function returns --- src/libs/actions/Workflow.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index 35fea58a9551..6a4c37158593 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -12,6 +12,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {ApprovalWorkflowOnyx, PersonalDetailsList, Policy} from '@src/types/onyx'; import type {Approver, Member} from '@src/types/onyx/ApprovalWorkflow'; import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; let currentApprovalWorkflow: ApprovalWorkflowOnyx | undefined; Onyx.connect({ @@ -325,7 +326,9 @@ function clearApprovalWorkflow() { Onyx.set(ONYXKEYS.APPROVAL_WORKFLOW, null); } -function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): Record { +type ApprovalWorkflowOnyxValidated = Omit & {approvers: Approver[]}; + +function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): approvalWorkflow is ApprovalWorkflowOnyxValidated { const errors: Record = {}; approvalWorkflow.approvers.forEach((approver, approverIndex) => { @@ -347,7 +350,9 @@ function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): Recor } Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {errors}); - return errors; + + // Return false if there are errors + return isEmptyObject(errors); } export { From 368109ef076708e06df9d356c05273e6601d2720 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 12:26:48 +0200 Subject: [PATCH 10/28] Parse email param --- src/libs/Navigation/linkingConfig/config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 52976576c088..3336c4a55be9 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -519,6 +519,9 @@ const config: LinkingOptions['config'] = { }, [SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EDIT]: { path: ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.route, + parse: { + firstApproverEmail: (firstApproverEmail: string) => decodeURIComponent(firstApproverEmail), + }, }, [SCREENS.WORKSPACE.WORKFLOWS_APPROVALS_EXPENSES_FROM]: { path: ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.route, From 90a83e25f7fbf53ff2ff6b730036529689a259cb Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 12:27:11 +0200 Subject: [PATCH 11/28] Clean the logic for opening edit screen --- .../workflows/WorkspaceWorkflowsPage.tsx | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 73854035e734..18b1314c9b1e 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -40,7 +40,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Beta} from '@src/types/onyx'; -import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import ToggleSettingOptionRow from './ToggleSettingsOptionRow'; import type {ToggleSettingOptionRowProps} from './ToggleSettingsOptionRow'; import {getAutoReportingFrequencyDisplayNames} from './WorkspaceAutoReportingFrequencyPage'; @@ -109,6 +108,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr ); return; } + Workflow.setApprovalWorkflow({ ...INITIAL_APPROVAL_WORKFLOW, availableMembers: approvalWorkflows.at(0)?.members ?? [], @@ -116,19 +116,6 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(route.params.policyID)); }, [approvalWorkflows, policy, route.params.policyID]); - const editApprovalAction = useCallback( - (workflow: ApprovalWorkflow) => { - Workflow.setApprovalWorkflow({ - ...workflow, - availableMembers: approvalWorkflows.at(0)?.members ?? [], - action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, - isLoading: false, - }); - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, workflow.approvers[0].email)); - }, - [approvalWorkflows, route.params.policyID], - ); - const optionItems: ToggleSettingOptionRowProps[] = useMemo(() => { const {accountNumber, addressName, bankName, bankAccountID} = policy?.achAccount ?? {}; const shouldShowBankAccount = !!bankAccountID && policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES; @@ -192,7 +179,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr > editApprovalAction(workflow)} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, workflow.approvers[0].email))} /> ))} @@ -323,7 +310,6 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr isPolicyAdmin, displayNameForAuthorizedPayer, route.params.policyID, - editApprovalAction, ]); const renderOptionItem = (item: ToggleSettingOptionRowProps, index: number) => ( From db71a314baa3774e34c83dba60ed1035a80aabe4 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 12:28:14 +0200 Subject: [PATCH 12/28] Hide header on edit screens --- .../approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx | 4 +++- .../approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx index fc9274db4b06..fb9da2fcdf57 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx @@ -242,7 +242,9 @@ function WorkspaceWorkflowsApprovalsApproverPageBeta({policy, personalDetails, i title={translate('workflowsPage.approver')} onBackButtonPress={goBack} /> - {translate('workflowsApproverPage.header')} + {approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && ( + {translate('workflowsApproverPage.header')} + )} - {translate('workflowsExpensesFromPage.header')} + {approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && ( + {translate('workflowsExpensesFromPage.header')} + )} Date: Tue, 13 Aug 2024 12:29:17 +0200 Subject: [PATCH 13/28] Attach the update and remove commands to edit screen --- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 89 +++++++++++++------ 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 6e42a24fb1f9..7a5f61fdf6b1 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; // eslint-disable-next-line no-restricted-imports import type {ScrollView} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -14,6 +14,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; +import {convertPolicyEmployeesToApprovalWorkflows} from '@libs/WorkflowUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -22,7 +23,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Approver} from '@src/types/onyx/ApprovalWorkflow'; +import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ApprovalWorkflowEditor from './ApprovalWorkflowEditor'; @@ -31,7 +32,9 @@ type WorkspaceWorkflowsApprovalsEditPageProps = WithPolicyAndFullscreenLoadingPr function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true, route}: WorkspaceWorkflowsApprovalsEditPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [approvalWorkflow, approvalWorkflowMetadata] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [approvalWorkflow] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); + const [initialApprovalWorkflow, setInitialApprovalWorkflow] = useState(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const formRef = useRef(null); @@ -43,22 +46,50 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return; } - if (!isEmptyObject(Workflow.validateApprovalWorkflow(approvalWorkflow))) { + if (!Workflow.validateApprovalWorkflow(approvalWorkflow)) { return; } - // Workflow.updateApprovalWorkflow(route.params.policyID, {...approvalWorkflow, approvers: approvalWorkflow.approvers as Approver[]}); + Workflow.updateApprovalWorkflow(route.params.policyID, approvalWorkflow); Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); }, [approvalWorkflow, route.params.policyID]); const removeApprovalWorkflow = useCallback(() => { - if (!approvalWorkflow) { + if (!initialApprovalWorkflow) { return; } - Workflow.removeApprovalWorkflow(route.params.policyID, {...approvalWorkflow, approvers: approvalWorkflow.approvers as Approver[]}); + // Remove the approval workflow using the initial data as it could be already edited + Workflow.removeApprovalWorkflow(route.params.policyID, initialApprovalWorkflow); Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); - }, [approvalWorkflow, route.params.policyID]); + }, [initialApprovalWorkflow, route.params.policyID]); + + // Set the initial approval workflow when the page is loaded + useEffect(() => { + if (!!initialApprovalWorkflow || !policy || !personalDetails) { + return; + } + + const defaultApprover = policy?.approver ?? policy.owner; + const workflows = convertPolicyEmployeesToApprovalWorkflows({ + employees: policy.employeeList ?? {}, + defaultApprover, + personalDetails, + }); + const currentApprovalWorkflow = workflows.find((workflow) => workflow.approvers.at(0)?.email === route.params.firstApproverEmail); + + if (!currentApprovalWorkflow) { + return Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + } + + Workflow.setApprovalWorkflow({ + ...currentApprovalWorkflow, + availableMembers: [...currentApprovalWorkflow.members, ...(workflows.at(0)?.members ?? [])], + action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, + isLoading: false, + }); + setInitialApprovalWorkflow(currentApprovalWorkflow); + }, [initialApprovalWorkflow, personalDetails, policy, route.params.firstApproverEmail, route.params.policyID]); return ( - {approvalWorkflowMetadata.status === 'loading' && } - {approvalWorkflow && ( - setIsDeleteModalVisible(true)} - policy={policy} - policyID={route.params.policyID} - ref={formRef} - /> + {approvalWorkflow ? ( + <> + setIsDeleteModalVisible(true)} + policy={policy} + policyID={route.params.policyID} + ref={formRef} + /> + { + formRef.current?.scrollTo({y: 0, animated: true}); + }} + isLoading={approvalWorkflow?.isLoading} + buttonText={translate('common.save')} + containerStyles={[styles.mb5, styles.mh5]} + /> + + ) : ( + )} - - { - formRef.current?.scrollTo({y: 0, animated: true}); - }} - isLoading={approvalWorkflow?.isLoading} - buttonText={translate('common.save')} - containerStyles={[styles.mb5, styles.mh5]} - /> Date: Tue, 13 Aug 2024 12:29:45 +0200 Subject: [PATCH 14/28] Adjust the logic on create screen for better clarity --- .../WorkspaceWorkflowsApprovalsCreatePage.tsx | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx index 25ca7c943841..855583bd88fb 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx @@ -21,7 +21,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Approver} from '@src/types/onyx/ApprovalWorkflow'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ApprovalWorkflowEditor from './ApprovalWorkflowEditor'; @@ -30,7 +29,7 @@ type WorkspaceWorkflowsApprovalsCreatePageProps = WithPolicyAndFullscreenLoading function WorkspaceWorkflowsApprovalsCreatePage({policy, isLoadingReportData = true, route}: WorkspaceWorkflowsApprovalsCreatePageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [approvalWorkflow, approvalWorkflowMetadata] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); + const [approvalWorkflow] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); const formRef = useRef(null); // eslint-disable-next-line rulesdir/no-negated-variables @@ -41,11 +40,11 @@ function WorkspaceWorkflowsApprovalsCreatePage({policy, isLoadingReportData = tr return; } - if (!isEmptyObject(Workflow.validateApprovalWorkflow(approvalWorkflow))) { + if (!Workflow.validateApprovalWorkflow(approvalWorkflow)) { return; } - Workflow.createApprovalWorkflow(route.params.policyID, {...approvalWorkflow, approvers: approvalWorkflow.approvers as Approver[]}); + Workflow.createApprovalWorkflow(route.params.policyID, approvalWorkflow); Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); }, [approvalWorkflow, route.params.policyID]); @@ -68,27 +67,29 @@ function WorkspaceWorkflowsApprovalsCreatePage({policy, isLoadingReportData = tr title={translate('workflowsCreateApprovalsPage.title')} onBackButtonPress={Navigation.goBack} /> - {approvalWorkflowMetadata.status === 'loading' && } - {approvalWorkflow && ( - + {approvalWorkflow ? ( + <> + + { + formRef.current?.scrollTo({y: 0, animated: true}); + }} + isLoading={approvalWorkflow?.isLoading} + buttonText={translate('workflowsCreateApprovalsPage.submitButton')} + containerStyles={[styles.mb5, styles.mh5]} + enabledWhenOffline + /> + + ) : ( + )} - - { - formRef.current?.scrollTo({y: 0, animated: true}); - }} - isLoading={approvalWorkflow?.isLoading} - buttonText={translate('workflowsCreateApprovalsPage.submitButton')} - containerStyles={[styles.mb5, styles.mh5]} - enabledWhenOffline - /> From 6a851974df5af455370ff040fe23648404e673d5 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 12:34:42 +0200 Subject: [PATCH 15/28] Adjust tests to newer convertApprovalWorkflowToPolicyEmployees logic --- tests/unit/WorkflowUtilsTest.ts | 55 ++++++++++----------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/tests/unit/WorkflowUtilsTest.ts b/tests/unit/WorkflowUtilsTest.ts index 81b1e559e5f6..cc5649a24fcf 100644 --- a/tests/unit/WorkflowUtilsTest.ts +++ b/tests/unit/WorkflowUtilsTest.ts @@ -13,7 +13,6 @@ const personalDetailsByEmail: PersonalDetailsList = {}; function buildPolicyEmployee(accountID: number, policyEmployee: Partial = {}): PolicyEmployee { return { email: `${accountID}@example.com`, - role: 'user', ...policyEmployee, }; } @@ -385,16 +384,12 @@ describe('WorkflowUtils', () => { approvers: [buildApprover(1)], isDefault: true, }; - const employeeList: PolicyEmployeeList = { - '1@example.com': buildPolicyEmployee(1, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com', role: 'admin'}), - '2@example.com': buildPolicyEmployee(2, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com', role: 'admin'}), - }; - const convertedEmployees = WorkflowUtils.convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList, type: 'create'}); + const convertedEmployees = WorkflowUtils.convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: 'create'}); expect(convertedEmployees).toEqual({ - '1@example.com': buildPolicyEmployee(1, {forwardsTo: '', submitsTo: '1@example.com', role: 'admin'}), - '2@example.com': buildPolicyEmployee(2, {forwardsTo: 'previous@example.com', submitsTo: '1@example.com', role: 'admin'}), + '1@example.com': buildPolicyEmployee(1, {forwardsTo: '', submitsTo: '1@example.com'}), + '2@example.com': buildPolicyEmployee(2, {submitsTo: '1@example.com'}), }); }); @@ -404,24 +399,16 @@ describe('WorkflowUtils', () => { approvers: [buildApprover(1, {forwardsTo: '2@example.com'}), buildApprover(2, {forwardsTo: '2@example.com'}), buildApprover(3)], isDefault: false, }; - const employeeList: PolicyEmployeeList = { - '1@example.com': buildPolicyEmployee(1, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com', role: 'admin'}), - '2@example.com': buildPolicyEmployee(2, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '3@example.com': buildPolicyEmployee(3, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '4@example.com': buildPolicyEmployee(4, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '5@example.com': buildPolicyEmployee(5, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '6@example.com': buildPolicyEmployee(6, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - }; - const convertedEmployees = WorkflowUtils.convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList, type: 'create'}); + const convertedEmployees = WorkflowUtils.convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: 'create'}); expect(convertedEmployees).toEqual({ - '1@example.com': buildPolicyEmployee(1, {forwardsTo: '2@example.com', submitsTo: 'previous@example.com', role: 'admin'}), - '2@example.com': buildPolicyEmployee(2, {forwardsTo: '3@example.com', submitsTo: 'previous@example.com'}), - '3@example.com': buildPolicyEmployee(3, {forwardsTo: '', submitsTo: 'previous@example.com'}), - '4@example.com': buildPolicyEmployee(4, {forwardsTo: 'previous@example.com', submitsTo: '1@example.com'}), - '5@example.com': buildPolicyEmployee(5, {forwardsTo: 'previous@example.com', submitsTo: '1@example.com'}), - '6@example.com': buildPolicyEmployee(6, {forwardsTo: 'previous@example.com', submitsTo: '1@example.com'}), + '1@example.com': buildPolicyEmployee(1, {forwardsTo: '2@example.com'}), + '2@example.com': buildPolicyEmployee(2, {forwardsTo: '3@example.com'}), + '3@example.com': buildPolicyEmployee(3, {forwardsTo: ''}), + '4@example.com': buildPolicyEmployee(4, {submitsTo: '1@example.com'}), + '5@example.com': buildPolicyEmployee(5, {submitsTo: '1@example.com'}), + '6@example.com': buildPolicyEmployee(6, {submitsTo: '1@example.com'}), }); }); @@ -431,24 +418,16 @@ describe('WorkflowUtils', () => { approvers: [buildApprover(1, {forwardsTo: '2@example.com'}), buildApprover(2, {forwardsTo: '2@example.com'}), buildApprover(3)], isDefault: false, }; - const employeeList: PolicyEmployeeList = { - '1@example.com': buildPolicyEmployee(1, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com', role: 'admin'}), - '2@example.com': buildPolicyEmployee(2, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '3@example.com': buildPolicyEmployee(3, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '4@example.com': buildPolicyEmployee(4, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '5@example.com': buildPolicyEmployee(5, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - '6@example.com': buildPolicyEmployee(6, {forwardsTo: 'previous@example.com', submitsTo: 'previous@example.com'}), - }; - const convertedEmployees = WorkflowUtils.convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, employeeList, type: 'remove'}); + const convertedEmployees = WorkflowUtils.convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: 'remove'}); expect(convertedEmployees).toEqual({ - '1@example.com': buildPolicyEmployee(1, {forwardsTo: '', submitsTo: 'previous@example.com', role: 'admin'}), - '2@example.com': buildPolicyEmployee(2, {forwardsTo: '', submitsTo: 'previous@example.com'}), - '3@example.com': buildPolicyEmployee(3, {forwardsTo: '', submitsTo: 'previous@example.com'}), - '4@example.com': buildPolicyEmployee(4, {forwardsTo: 'previous@example.com', submitsTo: ''}), - '5@example.com': buildPolicyEmployee(5, {forwardsTo: 'previous@example.com', submitsTo: ''}), - '6@example.com': buildPolicyEmployee(6, {forwardsTo: 'previous@example.com', submitsTo: ''}), + '1@example.com': buildPolicyEmployee(1, {forwardsTo: ''}), + '2@example.com': buildPolicyEmployee(2, {forwardsTo: ''}), + '3@example.com': buildPolicyEmployee(3, {forwardsTo: ''}), + '4@example.com': buildPolicyEmployee(4, {submitsTo: ''}), + '5@example.com': buildPolicyEmployee(5, {submitsTo: ''}), + '6@example.com': buildPolicyEmployee(6, {submitsTo: ''}), }); }); }); From b3f6d4c72537f393411b34b1965075c09cfb25c0 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 13:24:52 +0200 Subject: [PATCH 16/28] Fix navigation bug when going back from after removal --- .../approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 7a5f61fdf6b1..f6d76229a65c 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -51,7 +51,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true } Workflow.updateApprovalWorkflow(route.params.policyID, approvalWorkflow); - Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + Navigation.goBack(); }, [approvalWorkflow, route.params.policyID]); const removeApprovalWorkflow = useCallback(() => { @@ -61,7 +61,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true // Remove the approval workflow using the initial data as it could be already edited Workflow.removeApprovalWorkflow(route.params.policyID, initialApprovalWorkflow); - Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + Navigation.goBack(); }, [initialApprovalWorkflow, route.params.policyID]); // Set the initial approval workflow when the page is loaded @@ -79,7 +79,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const currentApprovalWorkflow = workflows.find((workflow) => workflow.approvers.at(0)?.email === route.params.firstApproverEmail); if (!currentApprovalWorkflow) { - return Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + return Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); } Workflow.setApprovalWorkflow({ From 7cc82a42c7f6c34f9962e2dd071c36fd88157323 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 14:15:51 +0200 Subject: [PATCH 17/28] Fix opening edit page with an url --- .../WorkspaceWorkflowsApprovalsCreatePage.tsx | 10 ++++--- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 27 +++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx index 855583bd88fb..a84391ad3f79 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx @@ -65,9 +65,12 @@ function WorkspaceWorkflowsApprovalsCreatePage({policy, isLoadingReportData = tr > { + Workflow.clearApprovalWorkflow(); + Navigation.goBack(); + }} /> - {approvalWorkflow ? ( + {approvalWorkflow && ( <> - ) : ( - )} + {!approvalWorkflow && } diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index f6d76229a65c..5a466f0f4d9e 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -79,17 +79,20 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const currentApprovalWorkflow = workflows.find((workflow) => workflow.approvers.at(0)?.email === route.params.firstApproverEmail); if (!currentApprovalWorkflow) { + Workflow.clearApprovalWorkflow(); return Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); } - Workflow.setApprovalWorkflow({ - ...currentApprovalWorkflow, - availableMembers: [...currentApprovalWorkflow.members, ...(workflows.at(0)?.members ?? [])], - action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, - isLoading: false, - }); + if (!approvalWorkflow) { + Workflow.setApprovalWorkflow({ + ...currentApprovalWorkflow, + availableMembers: [...currentApprovalWorkflow.members, ...(workflows.at(0)?.members ?? [])], + action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, + isLoading: false, + }); + } setInitialApprovalWorkflow(currentApprovalWorkflow); - }, [initialApprovalWorkflow, personalDetails, policy, route.params.firstApproverEmail, route.params.policyID]); + }, [approvalWorkflow, initialApprovalWorkflow, personalDetails, policy, route.params.firstApproverEmail, route.params.policyID]); return ( { + Workflow.clearApprovalWorkflow(); + Navigation.goBack(); + }} /> - {approvalWorkflow ? ( + {approvalWorkflow && ( <> - ) : ( - )} + {!initialApprovalWorkflow && } Date: Tue, 13 Aug 2024 15:21:41 +0200 Subject: [PATCH 18/28] Fix crash on ios --- .../approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx index 660be1ed7aef..413a8787ee34 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx @@ -68,7 +68,7 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat approvalWorkflow.members.map((member) => { const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); const accountID = Number(policyMemberEmailsToAccountIDs[member.email] ?? ''); - const isAdmin = policy?.employeeList?.[member.email].role === CONST.REPORT.ROLE.ADMIN; + const isAdmin = policy?.employeeList?.[member.email]?.role === CONST.REPORT.ROLE.ADMIN; return { text: member.displayName, @@ -89,7 +89,7 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat if (approvalWorkflow?.availableMembers) { const availableMembers = approvalWorkflow.availableMembers .map((member) => { - const isAdmin = policy?.employeeList?.[member.email].role === CONST.REPORT.ROLE.ADMIN; + const isAdmin = policy?.employeeList?.[member.email]?.role === CONST.REPORT.ROLE.ADMIN; const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); const accountID = Number(policyMemberEmailsToAccountIDs[member.email] ?? ''); From 04a1f3db5e52f1efd422d97995c834b06a58a97c Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 15:40:58 +0200 Subject: [PATCH 19/28] Fix removing members --- src/libs/WorkflowUtils.ts | 14 +++++++++++++- src/libs/actions/Workflow.ts | 4 ++-- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 7 ++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/libs/WorkflowUtils.ts b/src/libs/WorkflowUtils.ts index c145b18aff79..d9a3782987f6 100644 --- a/src/libs/WorkflowUtils.ts +++ b/src/libs/WorkflowUtils.ts @@ -152,6 +152,11 @@ type ConvertApprovalWorkflowToPolicyEmployeesParams = { */ approvalWorkflow: ApprovalWorkflow; + /** + * Members to remove from the approval workflow + */ + membersToRemove?: Member[]; + /** * Mode to use when converting the approval workflow */ @@ -159,7 +164,7 @@ type ConvertApprovalWorkflowToPolicyEmployeesParams = { }; /** Convert an approval workflow to a list of policy employees */ -function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type}: ConvertApprovalWorkflowToPolicyEmployeesParams): PolicyEmployeeList { +function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, membersToRemove, type}: ConvertApprovalWorkflowToPolicyEmployeesParams): PolicyEmployeeList { const updatedEmployeeList: PolicyEmployeeList = {}; const firstApprover = approvalWorkflow.approvers.at(0); @@ -182,6 +187,13 @@ function convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type}: Conv }; }); + membersToRemove?.forEach(({email}) => { + updatedEmployeeList[email] = { + ...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}), + submitsTo: '', + }; + }); + return updatedEmployeeList; } diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index 6a4c37158593..5b3c790b91f6 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -111,7 +111,7 @@ function createApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork API.write(WRITE_COMMANDS.CREATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData}); } -function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWorkflow) { +function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[]) { const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; if (!authToken || !policy) { @@ -119,7 +119,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork } const previousEmployeeList = {...policy.employeeList}; - const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE}); + const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE, membersToRemove}); const optimisticData: OnyxUpdate[] = [ { diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 5a466f0f4d9e..50b618f5469a 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -42,7 +42,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy); const updateApprovalWorkflow = useCallback(() => { - if (!approvalWorkflow) { + if (!approvalWorkflow || !initialApprovalWorkflow) { return; } @@ -50,9 +50,10 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return; } - Workflow.updateApprovalWorkflow(route.params.policyID, approvalWorkflow); + const membersToRemove = initialApprovalWorkflow.members.filter((initialMember) => !approvalWorkflow.members.some((member) => member.email === initialMember.email)); + Workflow.updateApprovalWorkflow(route.params.policyID, approvalWorkflow, membersToRemove); Navigation.goBack(); - }, [approvalWorkflow, route.params.policyID]); + }, [approvalWorkflow, initialApprovalWorkflow, route.params.policyID]); const removeApprovalWorkflow = useCallback(() => { if (!initialApprovalWorkflow) { From 48f2ac838e85558a2ec96137c6efcdfae446e884 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 13 Aug 2024 17:11:54 +0200 Subject: [PATCH 20/28] Improve the comment --- src/components/ApprovalWorkflowSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ApprovalWorkflowSection.tsx b/src/components/ApprovalWorkflowSection.tsx index 17cadd3826ae..17cde0306c50 100644 --- a/src/components/ApprovalWorkflowSection.tsx +++ b/src/components/ApprovalWorkflowSection.tsx @@ -15,7 +15,7 @@ type ApprovalWorkflowSectionProps = { /** Single workflow displayed in this component */ approvalWorkflow: ApprovalWorkflow; - /** Function to call when the user clicks on section */ + /** A function that is called when the section is pressed */ onPress: () => void; }; From bdbe28acc7b25d8cf25e8c1a06e7881a8a0b1fd3 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 14 Aug 2024 07:40:44 +0200 Subject: [PATCH 21/28] Add new parameter to UpdateWorkspaceApproval command - defaultApprover --- src/libs/API/parameters/WorkspaceApprovalParams.ts | 4 +++- src/libs/actions/Workflow.ts | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libs/API/parameters/WorkspaceApprovalParams.ts b/src/libs/API/parameters/WorkspaceApprovalParams.ts index 703d4976c045..0a84bc552bc4 100644 --- a/src/libs/API/parameters/WorkspaceApprovalParams.ts +++ b/src/libs/API/parameters/WorkspaceApprovalParams.ts @@ -12,7 +12,9 @@ type CreateWorkspaceApprovalParams = { employees: string; }; -type UpdateWorkspaceApprovalParams = CreateWorkspaceApprovalParams; +type UpdateWorkspaceApprovalParams = CreateWorkspaceApprovalParams & { + defaultApprover?: string; +}; type RemoveWorkspaceApprovalParams = CreateWorkspaceApprovalParams; diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index 5b3c790b91f6..c140d5c97909 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -170,7 +170,13 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork }, ]; - const parameters: UpdateWorkspaceApprovalParams = {policyID, authToken, employees: JSON.stringify(Object.values(updatedEmployees))}; + // const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers[0].email : undefined; + const parameters: UpdateWorkspaceApprovalParams = { + policyID, + authToken, + employees: JSON.stringify(Object.values(updatedEmployees)), + // defaultApprover: newDefaultApprover, + }; API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData}); } @@ -341,7 +347,7 @@ function validateApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx): appro } }); - if (!approvalWorkflow.members.length) { + if (!approvalWorkflow.members.length && !approvalWorkflow.isDefault) { errors.members = 'common.error.fieldRequired'; } From 53283032f4b7f9102831046c523a13503cae10eb Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 14 Aug 2024 07:51:22 +0200 Subject: [PATCH 22/28] Fix a bug that approvalWorkflow wasn't updated in some cases --- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 50b618f5469a..39c3543f0239 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -84,16 +84,14 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); } - if (!approvalWorkflow) { - Workflow.setApprovalWorkflow({ - ...currentApprovalWorkflow, - availableMembers: [...currentApprovalWorkflow.members, ...(workflows.at(0)?.members ?? [])], - action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, - isLoading: false, - }); - } + Workflow.setApprovalWorkflow({ + ...currentApprovalWorkflow, + availableMembers: [...currentApprovalWorkflow.members, ...(workflows.at(0)?.members ?? [])], + action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, + isLoading: false, + }); setInitialApprovalWorkflow(currentApprovalWorkflow); - }, [approvalWorkflow, initialApprovalWorkflow, personalDetails, policy, route.params.firstApproverEmail, route.params.policyID]); + }, [initialApprovalWorkflow, personalDetails, policy, route.params.firstApproverEmail, route.params.policyID]); return ( Date: Wed, 14 Aug 2024 07:58:05 +0200 Subject: [PATCH 23/28] Show not found page instead of going back --- .../WorkspaceWorkflowsApprovalsCreatePage.tsx | 5 +---- .../approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 10 +++------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx index a84391ad3f79..b5750e9e768f 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx @@ -65,10 +65,7 @@ function WorkspaceWorkflowsApprovalsCreatePage({policy, isLoadingReportData = tr > { - Workflow.clearApprovalWorkflow(); - Navigation.goBack(); - }} + onBackButtonPress={Navigation.goBack} /> {approvalWorkflow && ( <> diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 39c3543f0239..5f2ac16a8870 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -39,7 +39,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const formRef = useRef(null); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy); + const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy) || !approvalWorkflow; const updateApprovalWorkflow = useCallback(() => { if (!approvalWorkflow || !initialApprovalWorkflow) { @@ -80,8 +80,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const currentApprovalWorkflow = workflows.find((workflow) => workflow.approvers.at(0)?.email === route.params.firstApproverEmail); if (!currentApprovalWorkflow) { - Workflow.clearApprovalWorkflow(); - return Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + return Workflow.clearApprovalWorkflow(); } Workflow.setApprovalWorkflow({ @@ -110,10 +109,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true > { - Workflow.clearApprovalWorkflow(); - Navigation.goBack(); - }} + onBackButtonPress={Navigation.goBack} /> {approvalWorkflow && ( <> From 99938ed0325f24b3e051324e9c29424d79367047 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 14 Aug 2024 08:17:50 +0200 Subject: [PATCH 24/28] Remove unused import --- .../workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 5f2ac16a8870..f6e043ac0da8 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -21,7 +21,6 @@ import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPol import * as Workflow from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; From f76850a9d6dddd2efb81acd56c7c5d29525bac78 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 14 Aug 2024 09:37:25 +0200 Subject: [PATCH 25/28] Fix Not found page when removing the first approver --- .../approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx index fb9da2fcdf57..545c09f75313 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx @@ -188,7 +188,7 @@ function WorkspaceWorkflowsApprovalsApproverPageBeta({policy, personalDetails, i Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(route.params.policyID), CONST.NAVIGATION.TYPE.UP); } else { const firstApprover = approvalWorkflow?.approvers?.[0]?.email ?? ''; - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, firstApprover)); + Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, firstApprover)); } }, [approvalWorkflow, approverIndex, personalDetails, policy?.employeeList, route.params.policyID, selectedApproverEmail]); From d4d225d410545a94db475ae4691ac12481ac3668 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 14 Aug 2024 12:43:04 +0200 Subject: [PATCH 26/28] Uncomment changing the default approver --- src/libs/actions/Workflow.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index c140d5c97909..167e93630dd2 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -170,12 +170,12 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork }, ]; - // const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers[0].email : undefined; + const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers[0].email : undefined; const parameters: UpdateWorkspaceApprovalParams = { policyID, authToken, employees: JSON.stringify(Object.values(updatedEmployees)), - // defaultApprover: newDefaultApprover, + defaultApprover: newDefaultApprover, }; API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData}); } From 30b99d2a5f9ab0d9260b028631a69b35809cdff2 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 14 Aug 2024 12:47:11 +0200 Subject: [PATCH 27/28] Enable button when offline --- .../workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index f6e043ac0da8..25f288690c5d 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -128,6 +128,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true isLoading={approvalWorkflow?.isLoading} buttonText={translate('common.save')} containerStyles={[styles.mb5, styles.mh5]} + enabledWhenOffline /> )} From d18d2f0506a3068dc8ad651ead2717187c707d57 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 14 Aug 2024 13:06:50 +0200 Subject: [PATCH 28/28] Update default approver in optimisticData --- src/libs/actions/Workflow.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index 167e93630dd2..0b6c29c9890a 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -118,6 +118,8 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork return; } + const previousDefaultApprover = policy.approver ?? policy.owner; + const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers[0].email : undefined; const previousEmployeeList = {...policy.employeeList}; const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE, membersToRemove}); @@ -135,6 +137,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork value: { employeeList: updatedEmployees, pendingFields: {employeeList: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + ...(newDefaultApprover ? {approver: newDefaultApprover} : {}), }, }, ]; @@ -151,6 +154,7 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork value: { employeeList: previousEmployeeList, pendingFields: {employeeList: null}, + ...(newDefaultApprover ? {approver: previousDefaultApprover} : {}), }, }, ]; @@ -170,7 +174,6 @@ function updateApprovalWorkflow(policyID: string, approvalWorkflow: ApprovalWork }, ]; - const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers[0].email : undefined; const parameters: UpdateWorkspaceApprovalParams = { policyID, authToken,