diff --git a/assets/images/turtle-in-shell.svg b/assets/images/turtle-in-shell.svg new file mode 100644 index 000000000000..6c5a8e74bb31 --- /dev/null +++ b/assets/images/turtle-in-shell.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index e30fc8bce7b7..70cd0168aba8 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -113,6 +113,7 @@ import WalletAlt from '@assets/images/simple-illustrations/simple-illustration__ import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; import ExpensifyApprovedLogoLight from '@assets/images/subscription-details__approvedlogo--light.svg'; import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo.svg'; +import TurtleInShell from '@assets/images/turtle-in-shell.svg'; export { Abracadabra, @@ -230,4 +231,5 @@ export { AmexCompanyCards, MasterCardCompanyCards, VisaCompanyCards, + TurtleInShell, }; diff --git a/src/languages/en.ts b/src/languages/en.ts index 48953757deca..3890bfe54b9f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1347,6 +1347,11 @@ export default { approverInMultipleWorkflows: 'This member already belongs to another approval workflow. Any updates here will reflect there too.', approverCircularReference: ({name1, name2}: ApprovalWorkflowErrorParams) => `${name1} already approves reports to ${name2}. Please choose a different approver to avoid a circular workflow.`, + emptyContent: { + title: 'No members to display', + expensesFromSubtitle: 'All workspace members already belong to an existing approval workflow.', + approverSubtitle: 'All approvers belong to an existing workflow.', + }, }, workflowsDelayedSubmissionPage: { autoReportingErrorMessage: "Delayed submission couldn't be changed. Please try again or contact support.", diff --git a/src/languages/es.ts b/src/languages/es.ts index 2ab6deacda47..f1b1f4658865 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1353,6 +1353,11 @@ export default { approverInMultipleWorkflows: 'Este miembro ya pertenece a otro flujo de aprobación. Cualquier actualización aquí se reflejará allí también.', approverCircularReference: ({name1, name2}: ApprovalWorkflowErrorParams) => `${name1} ya aprueba informes a ${name2}. Por favor, elige un aprobador diferente para evitar un flujo de trabajo circular.`, + emptyContent: { + title: 'No hay miembros para mostrar', + expensesFromSubtitle: 'Todos los miembros del espacio de trabajo ya pertenecen a un flujo de aprobación existente.', + approverSubtitle: 'Todos los aprobadores pertenecen a un flujo de trabajo existente.', + }, }, workflowsDelayedSubmissionPage: { autoReportingErrorMessage: 'El parámetro de envío retrasado no pudo ser cambiado. Por favor, inténtelo de nuevo o contacte al soporte.', diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx index 986456c9be0f..9daf6a6a41eb 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApproverPage.tsx @@ -4,10 +4,12 @@ import type {SectionListData} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx, withOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; +import BlockingView from '@components/BlockingViews/BlockingView'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {FallbackAvatar} from '@components/Icon/Expensicons'; +import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import InviteMemberListItem from '@components/SelectionList/InviteMemberListItem'; @@ -30,6 +32,7 @@ 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 variables from '@styles/variables'; import * as Policy from '@userActions/Policy/Policy'; import * as Workflow from '@userActions/Workflow'; import CONST from '@src/CONST'; @@ -74,9 +77,8 @@ function WorkspaceWorkflowsApprovalsApproverPageWrapper(props: WorkspaceWorkflow function WorkspaceWorkflowsApprovalsApproverPageBeta({policy, personalDetails, isLoadingReportData = true, route}: WorkspaceWorkflowsApprovalsApproverPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); - const [approvalWorkflow, approvalWorkflowMetadata] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); + const [approvalWorkflow] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); const [selectedApproverEmail, setSelectedApproverEmail] = useState(undefined); // eslint-disable-next-line rulesdir/no-negated-variables @@ -165,6 +167,8 @@ function WorkspaceWorkflowsApprovalsApproverPageBeta({policy, personalDetails, i translate, ]); + const shouldShowListEmptyContent = !debouncedSearchTerm && approvalWorkflow && !sections[0].data.length; + const nextStep = useCallback(() => { if (selectedApproverEmail) { const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList); @@ -191,18 +195,23 @@ function WorkspaceWorkflowsApprovalsApproverPageBeta({policy, personalDetails, i } }, [approvalWorkflow, approverIndex, personalDetails, policy?.employeeList, route.params.policyID, selectedApproverEmail]); - const nextButton = useMemo( - () => ( + const button = useMemo(() => { + let buttonText = isInitialCreationFlow ? translate('common.next') : translate('common.save'); + + if (shouldShowListEmptyContent) { + buttonText = translate('common.buttonConfirm'); + } + + return ( - ), - [isInitialCreationFlow, nextStep, selectedApproverEmail, styles.flexBasisAuto, styles.flexGrow0, styles.flexReset, styles.flexShrink0, translate], - ); + ); + }, [isInitialCreationFlow, nextStep, selectedApproverEmail, shouldShowListEmptyContent, styles.flexBasisAuto, styles.flexGrow0, styles.flexReset, styles.flexShrink0, translate]); const goBack = useCallback(() => { if (isInitialCreationFlow) { @@ -221,6 +230,21 @@ function WorkspaceWorkflowsApprovalsApproverPageBeta({policy, personalDetails, i const headerMessage = useMemo(() => (searchTerm && !sections[0].data.length ? translate('common.noResultsFound') : ''), [searchTerm, sections, translate]); + const listEmptyContent = useMemo( + () => ( + + ), + [translate, styles.textSupporting, styles.pb10], + ); + return ( setDidScreenTransitionEnd(true)} > - {approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && ( + {approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && !shouldShowListEmptyContent && ( {translate('workflowsApproverPage.header')} )} diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx index f1a0f34dda01..e99164587da6 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx @@ -3,10 +3,12 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import type {SectionListData} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; +import BlockingView from '@components/BlockingViews/BlockingView'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {FallbackAvatar} from '@components/Icon/Expensicons'; +import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import InviteMemberListItem from '@components/SelectionList/InviteMemberListItem'; @@ -23,6 +25,7 @@ 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 variables from '@styles/variables'; import * as Workflow from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -50,14 +53,14 @@ type WorkspaceWorkflowsApprovalsExpensesFromPageProps = WithPolicyAndFullscreenL function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportData = true, route}: WorkspaceWorkflowsApprovalsExpensesFromPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); - const [approvalWorkflow, approvalWorkflowMetadata] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); + const [approvalWorkflow] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); const [selectedMembers, setSelectedMembers] = useState([]); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy); const isInitialCreationFlow = approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && !route.params.backTo; + const shouldShowListEmptyContent = approvalWorkflow && approvalWorkflow.availableMembers.length === 0; useEffect(() => { if (!approvalWorkflow?.members) { @@ -150,18 +153,23 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat Navigation.goBack(); }, [isInitialCreationFlow]); - const nextButton = useMemo( - () => ( + const button = useMemo(() => { + let buttonText = isInitialCreationFlow ? translate('common.next') : translate('common.save'); + + if (shouldShowListEmptyContent) { + buttonText = translate('common.buttonConfirm'); + } + + return ( Navigation.goBack() : nextStep} containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto]} enabledWhenOffline /> - ), - [isInitialCreationFlow, nextStep, selectedMembers.length, styles.flexBasisAuto, styles.flexGrow0, styles.flexReset, styles.flexShrink0, translate], - ); + ); + }, [isInitialCreationFlow, nextStep, selectedMembers.length, shouldShowListEmptyContent, styles.flexBasisAuto, styles.flexGrow0, styles.flexReset, styles.flexShrink0, translate]); const toggleMember = (member: SelectionListMember) => { const isAlreadySelected = selectedMembers.some((selectedOption) => selectedOption.login === member.login); @@ -170,6 +178,21 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat const headerMessage = useMemo(() => (searchTerm && !sections[0].data.length ? translate('common.noResultsFound') : ''), [searchTerm, sections, translate]); + const listEmptyContent = useMemo( + () => ( + + ), + [translate, styles.textSupporting, styles.pb10], + ); + return ( setDidScreenTransitionEnd(true)} > - {approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && ( + + {approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && !shouldShowListEmptyContent && ( {translate('workflowsExpensesFromPage.header')} )}