From 952ffd67683993ef2bda85ac092c0f811333f441 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 6 Jun 2025 12:41:51 +0530 Subject: [PATCH 01/17] May Workspace filter page multi-select --- src/hooks/useWorkspaceList.ts | 12 ++-- src/libs/PolicyUtils.ts | 6 +- .../SearchFiltersWorkspacePage.tsx | 71 +++++++++++++++---- 3 files changed, 67 insertions(+), 22 deletions(-) diff --git a/src/hooks/useWorkspaceList.ts b/src/hooks/useWorkspaceList.ts index 76d1a4f35f80..6cff4900a53c 100644 --- a/src/hooks/useWorkspaceList.ts +++ b/src/hooks/useWorkspaceList.ts @@ -21,12 +21,12 @@ type UseWorkspaceListParams = { policies: OnyxCollection; currentUserLogin: string | undefined; shouldShowPendingDeletePolicy: boolean; - selectedPolicyID: string | undefined; + selectedPolicyIDs: string[] | undefined; searchTerm: string; additionalFilter?: (policy: OnyxEntry) => boolean; }; -function useWorkspaceList({policies, currentUserLogin, selectedPolicyID, searchTerm, shouldShowPendingDeletePolicy, additionalFilter}: UseWorkspaceListParams) { +function useWorkspaceList({policies, currentUserLogin, selectedPolicyIDs, searchTerm, shouldShowPendingDeletePolicy, additionalFilter}: UseWorkspaceListParams) { const usersWorkspaces = useMemo(() => { if (!policies || isEmptyObject(policies)) { return []; @@ -54,16 +54,16 @@ function useWorkspaceList({policies, currentUserLogin, selectedPolicyID, searchT ], keyForList: policy?.id, isPolicyAdmin: isPolicyAdmin(policy), - isSelected: selectedPolicyID === policy?.id, + isSelected: policy?.id && selectedPolicyIDs ? selectedPolicyIDs.includes(policy.id) : false, })); - }, [policies, shouldShowPendingDeletePolicy, currentUserLogin, additionalFilter, selectedPolicyID]); + }, [policies, shouldShowPendingDeletePolicy, currentUserLogin, additionalFilter, selectedPolicyIDs]); const filteredAndSortedUserWorkspaces = useMemo( () => tokenizedSearch(usersWorkspaces, searchTerm, (policy) => [policy.text]).sort((policy1, policy2) => - sortWorkspacesBySelected({policyID: policy1.policyID, name: policy1.text}, {policyID: policy2.policyID, name: policy2.text}, selectedPolicyID), + sortWorkspacesBySelected({policyID: policy1.policyID, name: policy1.text}, {policyID: policy2.policyID, name: policy2.text}, selectedPolicyIDs), ), - [searchTerm, usersWorkspaces, selectedPolicyID], + [searchTerm, usersWorkspaces, selectedPolicyIDs], ); const sections = useMemo(() => { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index d08acf7e3e2f..e7e601958596 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1119,11 +1119,11 @@ function getSageIntacctCreditCards(policy?: Policy, selectedAccount?: string): S * @param workspace2 Details of the second workspace to be compared. * @param selectedWorkspaceID ID of the selected workspace which needs to be at the beginning. */ -const sortWorkspacesBySelected = (workspace1: WorkspaceDetails, workspace2: WorkspaceDetails, selectedWorkspaceID: string | undefined): number => { - if (workspace1.policyID === selectedWorkspaceID) { +const sortWorkspacesBySelected = (workspace1: WorkspaceDetails, workspace2: WorkspaceDetails, selectedWorkspaceIDs: string[] | undefined): number => { + if (workspace1.policyID && selectedWorkspaceIDs?.includes(workspace1?.policyID)) { return -1; } - if (workspace2.policyID === selectedWorkspaceID) { + if (workspace2.policyID && selectedWorkspaceIDs?.includes(workspace2.policyID)) { return 1; } return workspace1.name?.toLowerCase().localeCompare(workspace2.name?.toLowerCase() ?? '') ?? 0; diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx index 3fa85c51b5a0..d17daeebb7e5 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersWorkspacePage.tsx @@ -1,10 +1,12 @@ -import React from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; +import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import Button from '@components/Button'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; -import UserListItem from '@components/SelectionList/UserListItem'; +import UserSelectionListItem from '@components/SelectionList/Search/UserSelectionListItem'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -17,7 +19,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -const updateWorkspaceFilter = (policyID: string | null) => { +const updateWorkspaceFilter = (policyID: string[] | null) => { updateAdvancedFilters({ policyID, }); @@ -36,14 +38,44 @@ function SearchFiltersWorkspacePage() { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const shouldShowLoadingIndicator = isLoadingApp && !isOffline; + const [selectedOptions, setSelectedOptions] = useState(searchAdvancedFiltersForm?.policyID ?? []); + const {sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ policies, currentUserLogin, shouldShowPendingDeletePolicy: false, - selectedPolicyID: searchAdvancedFiltersForm?.policyID, + selectedPolicyIDs: selectedOptions, searchTerm: debouncedSearchTerm, }); + const selectWorkspace = useCallback( + (option: WorkspaceListItem) => { + const optionIndex = selectedOptions.findIndex((selectedOption: string) => { + const matchesPolicyId = selectedOption && selectedOption === option?.policyID; + // Below is just a boolean expression. + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return matchesPolicyId; + }); + + if (optionIndex === -1 && option?.policyID) { + setSelectedOptions([...selectedOptions, option.policyID]); + } else { + const newSelectedOptions = [...selectedOptions.slice(0, optionIndex), ...selectedOptions.slice(optionIndex + 1)]; + setSelectedOptions(newSelectedOptions); + } + }, + [selectedOptions], + ); + + const applyChanges = useCallback(() => { + const policyIds = selectedOptions.map((option) => (option ? option.toString() : undefined)).filter(Boolean) as string[]; + updateWorkspaceFilter(policyIds); + }, [selectedOptions]); + + const resetChanges = useCallback(() => { + updateWorkspaceFilter(null); + }, []); + return ( ) : ( - ListItem={UserListItem} + ListItem={UserSelectionListItem} sections={sections} - onSelectRow={(option) => { - if (option.policyID === searchAdvancedFiltersForm?.policyID || !option.policyID) { - updateWorkspaceFilter(null); - return; - } - updateWorkspaceFilter(option.policyID); - }} + canSelectMultiple + shouldUseDefaultRightHandSideCheckmark textInputLabel={shouldShowSearchInput ? translate('common.search') : undefined} textInputValue={searchTerm} onChangeText={setSearchTerm} + onSelectRow={selectWorkspace} headerMessage={shouldShowNoResultsFoundMessage ? translate('common.noResultsFound') : ''} - initiallyFocusedOptionKey={searchAdvancedFiltersForm?.policyID} + initiallyFocusedOptionKey={selectedOptions?.at(0)} showLoadingPlaceholder={isLoadingOnyxValue(policiesResult) || !didScreenTransitionEnd} + footerContent={ + <> +