diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 82724fd7114e..f13801258fdd 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -202,7 +202,6 @@ const CONST = { CUSTOM_FIELD_KEYS: {customField1: 'employeeUserID', customField2: 'employeePayrollID'}, ANDROID_PACKAGE_NAME, WORKSPACE_ENABLE_FEATURE_REDIRECT_DELAY: 100, - WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM_ROUTE: 'expenses-from', ANIMATED_HIGHLIGHT_ENTRY_DELAY: 50, ANIMATED_HIGHLIGHT_ENTRY_DURATION: 300, ANIMATED_HIGHLIGHT_START_DELAY: 10, diff --git a/src/ROUTES.ts b/src/ROUTES.ts index e2f6f949796d..67fe57f582e3 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -6,7 +6,7 @@ import type {TupleToUnion, ValueOf} from 'type-fest'; import type {UpperCaseCharacters} from 'type-fest/source/internal'; import type {SearchFilterKey, SearchQueryString, UserFriendlyKey} from './components/Search/types'; -import CONST from './CONST'; +import type CONST from './CONST'; import type {IOUAction, IOUType} from './CONST'; import type {ReplacementReason} from './libs/actions/Card'; import type {IOURequestType} from './libs/actions/IOU'; @@ -1727,11 +1727,10 @@ const ROUTES = { getRoute: (policyID: string, firstApproverEmail: string) => `workspaces/${policyID}/workflows/approvals/${encodeURIComponent(firstApproverEmail)}/edit` as const, }, WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM: { - route: `workspaces/:policyID/workflows/approvals/${CONST.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM_ROUTE}`, + route: 'workspaces/:policyID/workflows/approvals/expenses-from', - getRoute: (policyID: string, backTo?: string) => - // eslint-disable-next-line no-restricted-syntax -- Legacy route generation - getUrlWithBackToParam(`workspaces/${policyID}/workflows/approvals/${CONST.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM_ROUTE}` as const, backTo), + // eslint-disable-next-line no-restricted-syntax -- Legacy route generation + getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/workflows/approvals/expenses-from` as const, backTo), }, WORKSPACE_WORKFLOWS_APPROVALS_APPROVER: { route: 'workspaces/:policyID/workflows/approvals/approver', diff --git a/src/components/ApproverSelectionList.tsx b/src/components/ApproverSelectionList.tsx index ae6e44a40ef1..1f8d09e8bbcc 100644 --- a/src/components/ApproverSelectionList.tsx +++ b/src/components/ApproverSelectionList.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useMemo} from 'react'; import useDebouncedState from '@hooks/useDebouncedState'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -40,7 +40,6 @@ type ApproverSelectionListPageProps = { onSelectApprover?: (approvers: SelectionListApprover[]) => void; shouldShowLoadingPlaceholder?: boolean; shouldEnableHeaderMaxHeight?: boolean; - onSearchChange?: (searchTerm: string) => void; shouldUpdateFocusedIndex?: boolean; }; @@ -67,7 +66,6 @@ function ApproverSelectionList({ onSelectApprover, shouldShowLoadingPlaceholder, shouldEnableHeaderMaxHeight, - onSearchChange, shouldUpdateFocusedIndex = true, }: ApproverSelectionListPageProps) { const styles = useThemeStyles(); @@ -77,16 +75,6 @@ function ApproverSelectionList({ const shouldShowTextInput = shouldShowTextInputProp ?? allApprovers?.length >= CONST.STANDARD_LIST_ITEM_LIMIT; const lazyIllustrations = useMemoizedLazyIllustrations(['TurtleInShell']); - const handleSearchChange = useCallback( - (term: string) => { - setSearchTerm(term); - if (onSearchChange) { - onSearchChange(term); - } - }, - [onSearchChange, setSearchTerm], - ); - const selectedMembers = useMemo(() => allApprovers.filter((approver) => approver.isSelected), [allApprovers]); // eslint-disable-next-line rulesdir/no-negated-variables @@ -138,10 +126,10 @@ function ApproverSelectionList({ () => ({ label: shouldShowListEmptyContent ? undefined : translate('selectionList.findMember'), value: searchTerm, - onChangeText: handleSearchChange, + onChangeText: setSearchTerm, headerMessage: searchTerm && !data?.length ? translate('common.noResultsFound') : '', }), - [shouldShowListEmptyContent, translate, searchTerm, handleSearchChange, data?.length], + [shouldShowListEmptyContent, translate, searchTerm, setSearchTerm, data?.length], ); return ( @@ -165,9 +153,9 @@ function ApproverSelectionList({ {subtitle} { - const ineligibleInvitees = getIneligibleInvitees(employeeList); - const result: Record = {}; - for (const login of ineligibleInvitees) { - result[login] = true; - } - return result; -} - function getSortedTagKeys(policyTagList: OnyxEntry): Array { if (isEmptyObject(policyTagList)) { return []; @@ -1699,7 +1687,6 @@ export { getValidConnectedIntegration, getCountOfEnabledTagsOfList, getIneligibleInvitees, - getExcludedUsers, getMemberAccountIDsForWorkspace, getNumericValue, isMultiLevelTags, diff --git a/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx b/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx index 3f44087204bf..125b06ec5372 100644 --- a/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx +++ b/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx @@ -66,30 +66,6 @@ function WorkspaceInviteMessageComponent({ }: WorkspaceInviteMessageComponentProps) { const styles = useThemeStyles(); const {translate, formatPhoneNumber} = useLocalize(); - const policyName = policy?.name; - - const isWorkflowApprovalExpensesFromRoute = useMemo(() => { - if (!backTo || typeof backTo !== 'string') { - return false; - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - return (backTo as string).includes(CONST.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM_ROUTE); - }, [backTo]); - - const headerTitle = useMemo(() => { - if (isWorkflowApprovalExpensesFromRoute) { - return translate('workflowsExpensesFromPage.title'); - } - return translate('workspace.inviteMessage.confirmDetails'); - }, [isWorkflowApprovalExpensesFromRoute, translate]); - - const subtitle = useMemo(() => { - if (isWorkflowApprovalExpensesFromRoute) { - return undefined; - } - return policyName; - }, [isWorkflowApprovalExpensesFromRoute, policyName]); - const [formData, formDataResult] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM_DRAFT, {canBeMissing: true}); const [allPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); @@ -106,25 +82,22 @@ function WorkspaceInviteMessageComponent({ }); const [workspaceInviteRoleDraft = CONST.POLICY.ROLE.USER] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_ROLE_DRAFT}${policyID}`, {canBeMissing: true}); const isOnyxLoading = isLoadingOnyxValue(workspaceInviteMessageDraftResult, invitedEmailsToAccountIDsDraftResult, formDataResult); + const personalDetailsOfInvitedEmails = getPersonalDetailsForAccountIDs(Object.values(invitedEmailsToAccountIDsDraft ?? {}), allPersonalDetails ?? {}); + const memberNames = Object.values(personalDetailsOfInvitedEmails) + .map((personalDetail) => { + const displayName = getDisplayNameOrDefault(personalDetail, '', false); + if (displayName) { + return displayName; + } - const memberNames = useMemo(() => { - const personalDetailsOfInvitedEmails = getPersonalDetailsForAccountIDs(Object.values(invitedEmailsToAccountIDsDraft ?? {}), allPersonalDetails ?? {}); - return Object.values(personalDetailsOfInvitedEmails) - .map((personalDetail) => { - const displayName = getDisplayNameOrDefault(personalDetail, '', false); - if (displayName) { - return displayName; - } - - // We don't have login details for users who are not in the database yet - // So we need to fallback to their login from the invitedEmailsToAccountIDsDraft - const accountID = personalDetail.accountID; - const loginFromInviteMap = Object.entries(invitedEmailsToAccountIDsDraft ?? {}).find(([, id]) => id === accountID)?.[0]; + // We don't have login details for users who are not in the database yet + // So we need to fallback to their login from the invitedEmailsToAccountIDsDraft + const accountID = personalDetail.accountID; + const loginFromInviteMap = Object.entries(invitedEmailsToAccountIDsDraft ?? {}).find(([, id]) => id === accountID)?.[0]; - return loginFromInviteMap; - }) - .join(', '); - }, [invitedEmailsToAccountIDsDraft, allPersonalDetails]); + return loginFromInviteMap; + }) + .join(', '); const welcomeNoteSubject = useMemo( () => `# ${currentUserPersonalDetails?.displayName ?? ''} invited you to ${policy?.name ?? 'a workspace'}`, @@ -174,21 +147,6 @@ function WorkspaceInviteMessageComponent({ return; } - // If backTo is provided and it's the expenses-from route, navigate to approver screen - if (isWorkflowApprovalExpensesFromRoute) { - // Check if it's initial creation flow (backTo doesn't have a nested backTo param) - const backToStr = typeof backTo === 'string' ? backTo : ''; - const isInitialCreationFlow = !backToStr.includes('/expenses-from/'); - if (isInitialCreationFlow) { - // Navigate to approver screen for initial creation flow - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(policyID, 0)); - } else { - // For edit flow, just go back - Navigation.goBack(); - } - return; - } - if ((backTo as string)?.endsWith('members')) { Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.dismissModal()); return; @@ -218,6 +176,7 @@ function WorkspaceInviteMessageComponent({ return errorFields; }; + const policyName = policy?.name; const invitingMemberEmail = Object.keys(invitedEmailsToAccountIDsDraft ?? {}).at(0) ?? ''; const invitingMemberDetails = getPersonalDetailByEmail(invitingMemberEmail); const invitingMemberName = Str.removeSMSDomain(invitingMemberDetails?.displayName ?? ''); @@ -242,8 +201,8 @@ function WorkspaceInviteMessageComponent({ > {shouldShowBackButton && ( Navigation.dismissModal()} onBackButtonPress={() => Navigation.goBack(backTo)} @@ -259,9 +218,7 @@ function WorkspaceInviteMessageComponent({ shouldHideFixErrorsAlert addBottomSafeAreaPadding > - {(isInviteNewMemberStep || isWorkflowApprovalExpensesFromRoute) && ( - {translate('workspace.card.issueNewCard.inviteNewMember')} - )} + {isInviteNewMemberStep && {translate('workspace.card.issueNewCard.inviteNewMember')}} ([]); - const excludedUsers = useMemo(() => { - return getExcludedUsers(policy?.employeeList); - }, [policy?.employeeList]); - - const { - searchTerm: searchSelectorTerm, - setSearchTerm: setSearchSelectorTerm, - debouncedSearchTerm, - availableOptions, - areOptionsInitialized, - } = useSearchSelector({ - selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_MULTI, - searchContext: CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_MEMBER_INVITE, - includeUserToInvite: true, - excludeLogins: excludedUsers, - includeRecentReports: true, - shouldInitialize: true, - }); - - useEffect(() => { - searchInServer(searchSelectorTerm); - }, [searchSelectorTerm]); - // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !isPolicyAdmin(policy) || isPendingDeletePolicy(policy); const isInitialCreationFlow = approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && !route.params.backTo; @@ -76,53 +48,38 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat const personalDetailLogins = useDeepCompareRef(Object.fromEntries(Object.entries(personalDetails ?? {}).map(([id, details]) => [id, details?.login]))); useEffect(() => { - if (!approvalWorkflow?.members || selectedMembers.length > 0) { + if (!approvalWorkflow?.members) { return; } setSelectedMembers( approvalWorkflow.members.map((member) => { const policyMemberEmailsToAccountIDs = getMemberAccountIDsForWorkspace(policy?.employeeList); - let accountID = Number(policyMemberEmailsToAccountIDs[member.email] ?? ''); - const isPolicyMember = !!policy?.employeeList?.[member.email]; - - const personalDetail = getPersonalDetailByEmail(member.email); - - if (!isPolicyMember) { - const draftAccountID = invitedEmailsToAccountIDsDraft?.[member.email]; - if (draftAccountID) { - accountID = draftAccountID; - } else if (personalDetail?.accountID) { - accountID = personalDetail.accountID; - } - } - - const login = personalDetailLogins?.[accountID] ?? member.email; - const displayName = member.displayName ?? personalDetail?.displayName ?? member.email; - const avatar = member.avatar ?? personalDetail?.avatar; + const accountID = Number(policyMemberEmailsToAccountIDs[member.email] ?? ''); + const login = personalDetailLogins?.[accountID]; return { - text: Str.removeSMSDomain(displayName), + text: Str.removeSMSDomain(member.displayName), alternateText: member.email, keyForList: member.email, isSelected: true, login: member.email, - icons: [{source: avatar ?? icons.FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: Str.removeSMSDomain(displayName), id: accountID}], - rightElement: isPolicyMember ? ( + icons: [{source: member.avatar ?? icons.FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: Str.removeSMSDomain(member.displayName), id: accountID}], + rightElement: ( - ) : undefined, + ), }; }), ); - }, [approvalWorkflow?.members, icons.FallbackAvatar, invitedEmailsToAccountIDsDraft, personalDetailLogins, policy?.employeeList, policy?.owner, selectedMembers.length]); + }, [approvalWorkflow?.members, policy?.employeeList, policy?.owner, personalDetailLogins, translate, icons.FallbackAvatar]); + const approversEmail = useMemo(() => approvalWorkflow?.approvers.map((member) => member?.email), [approvalWorkflow?.approvers]); const allApprovers = useMemo(() => { const members: SelectionListApprover[] = [...selectedMembers]; - const approversEmail = approvalWorkflow?.approvers.map((member) => member?.email); if (!approvalWorkflow?.availableMembers) { return members; @@ -155,56 +112,8 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat members.push(...availableMembers); - if (debouncedSearchTerm && areOptionsInitialized) { - if (availableOptions.userToInvite) { - const userToInvite = availableOptions.userToInvite; - const isAlreadySelected = selectedMembers.some((selectedOption) => selectedOption.login === userToInvite.login); - if (!isAlreadySelected) { - members.push({ - text: userToInvite.text ?? userToInvite.login ?? '', - alternateText: userToInvite.login ?? '', - keyForList: userToInvite.login ?? '', - isSelected: false, - login: userToInvite.login ?? '', - icons: userToInvite.icons ?? [], - }); - } - } - - const searchResults = [...availableOptions.recentReports, ...availableOptions.personalDetails].filter((option) => { - const isMember = policy?.employeeList?.[option.login ?? '']; - const isAlreadyInList = members.some((m) => m.login === option.login); - return !isMember && !isAlreadyInList; - }); - - for (const option of searchResults) { - members.push({ - text: option.text ?? option.login ?? '', - alternateText: option.login ?? '', - keyForList: option.login ?? '', - isSelected: false, - login: option.login ?? '', - icons: option.icons ?? [], - }); - } - } - return members; - }, [ - selectedMembers, - approvalWorkflow?.availableMembers, - approvalWorkflow?.approvers, - policy?.employeeList, - policy?.owner, - policy?.preventSelfApproval, - personalDetailLogins, - debouncedSearchTerm, - areOptionsInitialized, - availableOptions.userToInvite, - availableOptions.recentReports, - availableOptions.personalDetails, - icons.FallbackAvatar, - ]); + }, [selectedMembers, approvalWorkflow?.availableMembers, policy?.employeeList, policy?.owner, policy?.preventSelfApproval, personalDetailLogins, icons.FallbackAvatar, approversEmail]); const goBack = useCallback(() => { let backTo; @@ -217,71 +126,15 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat }, [isInitialCreationFlow, route.params.policyID, firstApprover, approvalWorkflow?.action]); const nextStep = useCallback(() => { - const existingMembers: Member[] = []; - const usersToInvite: Array<{email: string; accountID?: number}> = []; - - for (const member of selectedMembers) { - if (!member.login) { - continue; - } - const isPolicyMember = policy?.employeeList?.[member.login]; - if (isPolicyMember) { - existingMembers.push({ - displayName: member.text ?? '', - avatar: member.icons?.at(0)?.source, - email: member.login, - }); - } else { - const iconId = member.icons?.at(0)?.id; - const accountID = typeof iconId === 'number' ? iconId : undefined; - usersToInvite.push({ - email: member.login, - accountID, - }); - } - } - - const normalizedExistingMembers: Member[] = existingMembers.map((member) => ({ - ...member, - avatar: typeof member.avatar === 'string' ? member.avatar : undefined, - })); - - const allMembers: Member[] = [ - ...normalizedExistingMembers, - ...usersToInvite.map((user) => ({ - displayName: user.email, - email: user.email, - avatar: undefined, - })), - ]; - setApprovalWorkflowMembers(allMembers); - - if (usersToInvite.length > 0) { - const invitedEmailsToAccountIDs: Record = {}; - for (const user of usersToInvite) { - if (!user.email) { - continue; - } - if (user.accountID) { - invitedEmailsToAccountIDs[user.email] = user.accountID; - } - } - - setWorkspaceInviteMembersDraft(route.params.policyID, invitedEmailsToAccountIDs); - - const backToRoute = isInitialCreationFlow - ? ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(route.params.policyID) - : ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(route.params.policyID, route.params.backTo); - Navigation.navigate(ROUTES.WORKSPACE_INVITE_MESSAGE.getRoute(route.params.policyID, backToRoute)); - return; - } + const members: Member[] = selectedMembers.map((member) => ({displayName: member.text ?? '', avatar: member.icons?.at(0)?.source, email: member.login ?? ''})); + setApprovalWorkflowMembers(members); if (isInitialCreationFlow) { Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(route.params.policyID, 0)); } else { goBack(); } - }, [route.params.policyID, route.params.backTo, selectedMembers, isInitialCreationFlow, goBack, policy?.employeeList]); + }, [route.params.policyID, selectedMembers, isInitialCreationFlow, goBack]); const button = useMemo(() => { let buttonText = isInitialCreationFlow ? translate('common.next') : translate('common.save'); @@ -324,12 +177,10 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat allApprovers={allApprovers} listEmptyContentSubtitle={translate('workflowsPage.emptyContent.expensesFromSubtitle')} allowMultipleSelection - shouldShowTextInput onSelectApprover={toggleMember} footerContent={button} shouldShowLoadingPlaceholder={isLoadingApprovalWorkflow} shouldEnableHeaderMaxHeight - onSearchChange={setSearchSelectorTerm} shouldUpdateFocusedIndex={false} />