diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index d5bf29943fb4d..cb4a2445bbd89 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -164,6 +164,7 @@ import type {PopoverMenuItem} from './PopoverMenu'; import {PressableWithFeedback} from './Pressable'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import {useSearchContext} from './Search/SearchContext'; +import {useSearchSelectionContext} from './Search/SearchSelectionContext'; import AnimatedSettlementButton from './SettlementButton/AnimatedSettlementButton'; import Text from './Text'; @@ -427,7 +428,8 @@ function MoneyReportHeader({ typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.HOLD | typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT | typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT_BULK > | null>(null); - const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey, currentSearchHash, currentSearchResults} = useSearchContext(); + const {currentSearchQueryJSON, currentSearchKey, currentSearchHash, currentSearchResults} = useSearchContext(); + const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions} = useSearchSelectionContext(); const shouldCalculateTotals = useSearchShouldCalculateTotals(currentSearchKey, currentSearchQueryJSON?.hash, true); const [network] = useOnyx(ONYXKEYS.NETWORK); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 805c6b04d6559..5b43a58c793ed 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -87,6 +87,7 @@ import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import MoneyRequestReportTransactionsNavigation from './MoneyRequestReportView/MoneyRequestReportTransactionsNavigation'; import {usePersonalDetails} from './OnyxListItemProvider'; import {useSearchContext} from './Search/SearchContext'; +import {useSearchSelectionContext} from './Search/SearchSelectionContext'; import {useWideRHPState} from './WideRHPContextProvider'; type MoneyRequestHeaderProps = { @@ -149,7 +150,8 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const isOnHold = isOnHoldTransactionUtils(transaction); const isDuplicate = isDuplicateTransactionUtils(transaction, email ?? '', accountID, report, policy, transactionViolations); const reportID = report?.reportID; - const {removeTransaction, currentSearchHash} = useSearchContext(); + const {currentSearchHash} = useSearchContext(); + const {removeTransaction} = useSearchSelectionContext(); const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction, originalTransaction); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index d3694c553ed33..55f112b6af4a4 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -17,7 +17,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import {PressableWithFeedback} from '@components/Pressable'; import ScrollView from '@components/ScrollView'; -import {useSearchContext} from '@components/Search/SearchContext'; +import {useSearchSelectionContext} from '@components/Search/SearchSelectionContext'; import Text from '@components/Text'; import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -196,7 +196,7 @@ function MoneyRequestReportActionsList({ const [enableScrollToEnd, setEnableScrollToEnd] = useState(false); const [lastActionEventId, setLastActionEventId] = useState(''); - const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); + const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchSelectionContext(); useFilterSelectedTransactions(transactions); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index ebfc919e2ead3..1f233504857ad 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -8,7 +8,7 @@ import Checkbox from '@components/Checkbox'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {useSearchContext} from '@components/Search/SearchContext'; +import {useSearchSelectionContext} from '@components/Search/SearchSelectionContext'; import type {SortOrder} from '@components/Search/types'; import Text from '@components/Text'; import {useWideRHPActions} from '@components/WideRHPContextProvider'; @@ -207,7 +207,7 @@ function MoneyRequestReportTransactionList({ return hasPendingDeletionTransaction || transactions.some(getTransactionPendingAction); }, [hasPendingDeletionTransaction, transactions]); - const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); + const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchSelectionContext(); useHandleSelectionMode(selectedTransactionIDs); const isMobileSelectionModeEnabled = useMobileSelectionMode(); diff --git a/src/components/Search/SearchBulkActionsModalContext.tsx b/src/components/Search/SearchBulkActionsModalContext.tsx new file mode 100644 index 0000000000000..8cb639df151d5 --- /dev/null +++ b/src/components/Search/SearchBulkActionsModalContext.tsx @@ -0,0 +1,29 @@ +import React, {createContext, useContext} from 'react'; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + +type RejectModalActionType = ValueOf | ValueOf; + +export type SearchBulkActionsModalContextValue = { + setIsOfflineModalVisible: (visible: boolean) => void; + setRejectModalAction: (action: RejectModalActionType | null) => void; + setIsHoldEducationalModalVisible: (visible: boolean) => void; + setIsDownloadErrorModalVisible: (visible: boolean) => void; + setEmptyReportsCount: (count: number) => void; +}; + +const defaultValue: SearchBulkActionsModalContextValue = { + setIsOfflineModalVisible: () => {}, + setRejectModalAction: () => {}, + setIsHoldEducationalModalVisible: () => {}, + setIsDownloadErrorModalVisible: () => {}, + setEmptyReportsCount: () => {}, +}; + +const SearchBulkActionsModalContext = createContext(defaultValue); + +function useSearchBulkActionsModal() { + return useContext(SearchBulkActionsModalContext); +} + +export {SearchBulkActionsModalContext, useSearchBulkActionsModal}; diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 968a6febe7eeb..0d7a4dc4584b5 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,20 +1,17 @@ -import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useMemo, useState} from 'react'; // We need direct access to useOnyx from react-native-onyx to avoid circular dependencies in SearchContext // eslint-disable-next-line no-restricted-imports import {useOnyx} from 'react-native-onyx'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useTodos from '@hooks/useTodos'; -import {isMoneyRequestReport} from '@libs/ReportUtils'; -import {getSuggestedSearches, isTodoSearch, isTransactionListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; +import {getSuggestedSearches, isTodoSearch} from '@libs/SearchUIUtils'; import type {SearchKey} from '@libs/SearchUIUtils'; -import {hasValidModifiedAmount} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchResults} from '@src/types/onyx'; import type {SearchResultsInfo} from '@src/types/onyx/SearchResults'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {SearchContextData, SearchContextProps, SearchQueryJSON, SelectedTransactions} from './types'; +import type {SearchContextData, SearchContextProps, SearchQueryJSON} from './types'; // Default search info when building from live data // Used for to-do searches where we build SearchResults from live Onyx data instead of API snapshots @@ -35,47 +32,29 @@ const defaultSearchContextData: SearchContextData = { currentSearchKey: undefined, currentSearchQueryJSON: undefined, currentSearchResults: undefined, - selectedTransactions: {}, - selectedTransactionIDs: [], - selectedReports: [], isOnSearch: false, - shouldTurnOffSelectionMode: false, shouldResetSearchQuery: false, }; const defaultSearchContext: SearchContextProps = { ...defaultSearchContextData, lastSearchType: undefined, - areAllMatchingItemsSelected: false, - showSelectAllMatchingItems: false, shouldShowFiltersBarLoading: false, currentSearchResults: undefined, shouldUseLiveData: false, setLastSearchType: () => {}, setCurrentSearchHashAndKey: () => {}, setCurrentSearchQueryJSON: () => {}, - setSelectedTransactions: () => {}, - removeTransaction: () => {}, - clearSelectedTransactions: () => {}, setShouldShowFiltersBarLoading: () => {}, - shouldShowSelectAllMatchingItems: () => {}, - selectAllMatchingItems: () => {}, setShouldResetSearchQuery: () => {}, }; const SearchContext = React.createContext(defaultSearchContext); function SearchContextProvider({children}: ChildrenProps) { - const [showSelectAllMatchingItems, shouldShowSelectAllMatchingItems] = useState(false); - const [areAllMatchingItemsSelected, selectAllMatchingItems] = useState(false); const [shouldShowFiltersBarLoading, setShouldShowFiltersBarLoading] = useState(false); const [lastSearchType, setLastSearchType] = useState(undefined); const [searchContextData, setSearchContextData] = useState(defaultSearchContextData); - const areTransactionsEmpty = useRef(true); - - // Use a ref to access searchContextData in callbacks without causing callback reference changes - const searchContextDataRef = useRef(searchContextData); - searchContextDataRef.current = searchContextData; const [snapshotSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchContextData.currentSearchHash}`); const todoSearchResultsData = useTodos(); @@ -136,131 +115,6 @@ function SearchContextProvider({children}: ChildrenProps) { }); }, []); - const setSelectedTransactions: SearchContextProps['setSelectedTransactions'] = useCallback((selectedTransactions, data = []) => { - if (selectedTransactions instanceof Array) { - if (!selectedTransactions.length && areTransactionsEmpty.current) { - areTransactionsEmpty.current = true; - return; - } - areTransactionsEmpty.current = false; - return setSearchContextData((prevState) => ({ - ...prevState, - selectedTransactionIDs: selectedTransactions, - })); - } - - // When selecting transactions, we also need to manage the reports to which these transactions belong. This is done to ensure proper exporting to CSV. - let selectedReports: SearchContextProps['selectedReports'] = []; - - if (data.length && data.every(isTransactionReportGroupListItemType)) { - selectedReports = data - .filter((item) => { - if (!isMoneyRequestReport(item)) { - return false; - } - if (item.transactions.length === 0) { - return !!item.keyForList && selectedTransactions[item.keyForList]?.isSelected; - } - return item.transactions.every(({keyForList}) => selectedTransactions[keyForList]?.isSelected); - }) - .map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, total = CONST.DEFAULT_NUMBER_ID, policyID, allActions = [action], currency, chatReportID}) => ({ - reportID, - action, - total, - policyID, - allActions, - currency, - chatReportID, - })); - } else if (data.length && data.every(isTransactionListItemType)) { - selectedReports = data - .filter(({keyForList}) => !!keyForList && selectedTransactions[keyForList]?.isSelected) - .map((item) => { - const total = hasValidModifiedAmount(item) ? Number(item.modifiedAmount) : (item.amount ?? CONST.DEFAULT_NUMBER_ID); - const action = item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW; - - return { - reportID: item.reportID, - action, - total, - policyID: item.policyID, - allActions: item.allActions ?? [action], - currency: item.currency, - chatReportID: item.report?.chatReportID, - }; - }); - } - - setSearchContextData((prevState) => ({ - ...prevState, - selectedTransactions, - shouldTurnOffSelectionMode: false, - selectedReports, - })); - }, []); - - const clearSelectedTransactions: SearchContextProps['clearSelectedTransactions'] = useCallback( - (searchHashOrClearIDsFlag, shouldTurnOffSelectionMode = false) => { - if (typeof searchHashOrClearIDsFlag === 'boolean') { - setSelectedTransactions([]); - return; - } - - const data = searchContextDataRef.current; - - if (searchHashOrClearIDsFlag === data.currentSearchHash) { - return; - } - - if (data.selectedReports.length === 0 && isEmptyObject(data.selectedTransactions) && !data.shouldTurnOffSelectionMode) { - return; - } - setSearchContextData((prevState) => ({ - ...prevState, - shouldTurnOffSelectionMode, - selectedTransactions: {}, - selectedReports: [], - })); - - // Unselect all transactions and hide the "select all matching items" option - shouldShowSelectAllMatchingItems(false); - selectAllMatchingItems(false); - }, - [setSelectedTransactions], - ); - - const removeTransaction: SearchContextProps['removeTransaction'] = useCallback( - (transactionID) => { - if (!transactionID) { - return; - } - const selectedTransactionIDs = searchContextData.selectedTransactionIDs; - - if (!isEmptyObject(searchContextData.selectedTransactions)) { - const newSelectedTransactions = Object.entries(searchContextData.selectedTransactions).reduce((acc, [key, value]) => { - if (key === transactionID) { - return acc; - } - acc[key] = value; - return acc; - }, {} as SelectedTransactions); - - setSearchContextData((prevState) => ({ - ...prevState, - selectedTransactions: newSelectedTransactions, - })); - } - - if (selectedTransactionIDs.length > 0) { - setSearchContextData((prevState) => ({ - ...prevState, - selectedTransactionIDs: selectedTransactionIDs.filter((ID) => transactionID !== ID), - })); - } - }, - [searchContextData.selectedTransactionIDs, searchContextData.selectedTransactions], - ); - const setShouldResetSearchQuery = useCallback((shouldReset: boolean) => { setSearchContextData((prevState) => ({ ...prevState, @@ -273,35 +127,22 @@ function SearchContextProvider({children}: ChildrenProps) { ...searchContextData, currentSearchResults, shouldUseLiveData, - removeTransaction, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, - setSelectedTransactions, - clearSelectedTransactions, shouldShowFiltersBarLoading, setShouldShowFiltersBarLoading, lastSearchType, setLastSearchType, - showSelectAllMatchingItems, - shouldShowSelectAllMatchingItems, - areAllMatchingItemsSelected, - selectAllMatchingItems, setShouldResetSearchQuery, }), [ searchContextData, currentSearchResults, shouldUseLiveData, - removeTransaction, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, - setSelectedTransactions, - clearSelectedTransactions, shouldShowFiltersBarLoading, lastSearchType, - shouldShowSelectAllMatchingItems, - showSelectAllMatchingItems, - areAllMatchingItemsSelected, setShouldResetSearchQuery, ], ); @@ -309,11 +150,6 @@ function SearchContextProvider({children}: ChildrenProps) { return {children}; } -/** - * Note: `selectedTransactionIDs` and `selectedTransactions` are two separate properties. - * Setting or clearing one of them does not influence the other. - * IDs should be used if transaction details are not required. - */ function useSearchContext() { return useContext(SearchContext); } diff --git a/src/components/Search/SearchPageFooter.tsx b/src/components/Search/SearchPageFooter.tsx index 95ba35a3214dd..9ecb9216ca0a6 100644 --- a/src/components/Search/SearchPageFooter.tsx +++ b/src/components/Search/SearchPageFooter.tsx @@ -7,15 +7,11 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useSearchFooter from '@hooks/useSearchFooter'; import {convertToDisplayString} from '@libs/CurrencyUtils'; -type SearchPageFooterProps = { - count: number | undefined; - total: number | undefined; - currency: string | undefined; -}; - -function SearchPageFooter({count, total, currency}: SearchPageFooterProps) { +function SearchPageFooter() { + const {count, total, currency, shouldShow} = useSearchFooter(); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -26,6 +22,10 @@ function SearchPageFooter({count, total, currency}: SearchPageFooterProps) { const valueTextStyle = useMemo(() => (isOffline ? [styles.textLabelSupporting, styles.labelStrong] : [styles.labelStrong]), [isOffline, styles]); + if (!shouldShow) { + return null; + } + return ( void; onSearchRouterFocus?: () => void; - headerButtonsOptions: Array>; handleSearch: (value: string) => void; isMobileSelectionModeEnabled: boolean; currentSelectedPolicyID?: string | undefined; currentSelectedReportID?: string | undefined; - confirmPayment?: (paymentType: PaymentMethodType | undefined) => void; latestBankItems?: BankAccountMenuItem[] | undefined; }; @@ -35,16 +32,15 @@ function SearchPageHeader({ searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, - headerButtonsOptions, handleSearch, isMobileSelectionModeEnabled, currentSelectedPolicyID, currentSelectedReportID, - confirmPayment, latestBankItems, }: SearchPageHeaderProps) { + const {headerButtonsOptions, onBulkPaySelected} = useSearchBulkActions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {selectedTransactions} = useSearchContext(); + const {selectedTransactions} = useSearchSelectionContext(); const {translate} = useLocalize(); const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); @@ -84,7 +80,7 @@ function SearchPageHeader({ itemsLength={selectedItemsCount} currentSelectedPolicyID={currentSelectedPolicyID} currentSelectedReportID={currentSelectedReportID} - confirmPayment={confirmPayment} + confirmPayment={onBulkPaySelected} latestBankItems={latestBankItems} /> ); diff --git a/src/components/Search/SearchPageHeader/SearchSelectionBar.tsx b/src/components/Search/SearchPageHeader/SearchSelectionBar.tsx new file mode 100644 index 0000000000000..d2bc8efe15c98 --- /dev/null +++ b/src/components/Search/SearchPageHeader/SearchSelectionBar.tsx @@ -0,0 +1,132 @@ +import {isUserValidatedSelector} from '@selectors/Account'; +import React, {useContext} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; +import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider'; +import KYCWall from '@components/KYCWall'; +import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; +import {LockedAccountContext} from '@components/LockedAccountModalProvider'; +import {useSearchSelectionContext} from '@components/Search/SearchSelectionContext'; +import type {BankAccountMenuItem, SearchQueryJSON} from '@components/Search/types'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import usePolicy from '@hooks/usePolicy'; +import useSearchBulkActions from '@hooks/useSearchBulkActions'; +import useSortedActiveAdminPolicies from '@hooks/useSortedActiveAdminPolicies'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {handleBulkPayItemSelected} from '@libs/actions/Search'; +import Navigation from '@libs/Navigation/Navigation'; +import {isExpenseReport} from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; + +type SearchSelectionBarProps = { + queryJSON?: SearchQueryJSON; + currentSelectedPolicyID?: string | undefined; + currentSelectedReportID?: string | undefined; + latestBankItems?: BankAccountMenuItem[] | undefined; +}; + +function SearchSelectionBar({queryJSON, currentSelectedPolicyID, currentSelectedReportID, latestBankItems}: SearchSelectionBarProps) { + const {headerButtonsOptions, onBulkPaySelected} = useSearchBulkActions(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {selectedTransactions, selectAllMatchingItems, areAllMatchingItemsSelected, showSelectAllMatchingItems} = useSearchSelectionContext(); + const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); + const currentPolicy = usePolicy(currentSelectedPolicyID); + const kycWallRef = useContext(KYCWallContext); + const {isDelegateAccessRestricted} = useDelegateNoAccessState(); + const {showDelegateNoAccessModal} = useDelegateNoAccessActions(); + const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); + const activeAdminPolicies = useSortedActiveAdminPolicies(); + const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector}); + const [selectedIOUReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentSelectedReportID}`); + const isCurrentSelectedExpenseReport = isExpenseReport(currentSelectedReportID); + + const isExpenseReportType = queryJSON?.type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT; + const selectedItemsCount = (() => { + if (!selectedTransactions) { + return 0; + } + if (isExpenseReportType) { + const reportIDs = new Set( + Object.values(selectedTransactions) + .map((transaction) => transaction?.reportID) + .filter((reportID): reportID is string => !!reportID), + ); + return reportIDs.size; + } + return selectedTransactionsKeys.length; + })(); + + const selectionButtonText = areAllMatchingItemsSelected ? translate('search.exportAll.allMatchingItemsSelected') : translate('workspace.common.selected', {count: selectedItemsCount}); + + return ( + + onBulkPaySelected?.(paymentType)} + > + {(triggerKYCFlow, buttonRef) => ( + + null} + shouldAlwaysShowDropdownMenu + buttonSize={CONST.DROPDOWN_BUTTON_SIZE.SMALL} + customText={selectionButtonText} + options={headerButtonsOptions} + onSubItemSelected={(subItem) => + handleBulkPayItemSelected({ + item: subItem, + triggerKYCFlow, + isAccountLocked, + showLockedAccountModal, + policy: currentPolicy, + latestBankItems, + activeAdminPolicies, + isUserValidated, + isDelegateAccessRestricted, + showDelegateNoAccessModal, + confirmPayment: onBulkPaySelected, + }) + } + isSplitButton={false} + buttonRef={buttonRef} + anchorAlignment={{ + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, + }} + sentryLabel={CONST.SENTRY_LABEL.SEARCH.BULK_ACTIONS_DROPDOWN} + /> + {!areAllMatchingItemsSelected && showSelectAllMatchingItems && ( +