diff --git a/src/CONST/index.ts b/src/CONST/index.ts index ca292aafea3f..149283e44f7f 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -8829,22 +8829,11 @@ const CONST = { SELECT_ALL_BUTTON: 'Search-SelectAllButton', TYPE_MENU_BUTTON: 'Search-TypeMenuButton', FILTER_DISPLAY: 'Search-FilterDisplay', - FILTER_TYPE: 'Search-FilterType', - FILTER_STATUS: 'Search-FilterStatus', - FILTER_DATE: 'Search-FilterDate', - FILTER_FROM: 'Search-FilterFrom', - FILTER_WORKSPACE: 'Search-FilterWorkspace', FILTER_GROUP_BY: 'Search-FilterGroupBy', FILTER_SORT_BY: 'Search-FilterSortBy', FILTER_GROUP_CURRENCY: 'Search-FilterGroupCurrency', FILTER_VIEW: 'Search-FilterView', FILTER_LIMIT: 'Search-FilterLimit', - FILTER_FEED: 'Search-FilterFeed', - FILTER_POSTED: 'Search-FilterPosted', - FILTER_WITHDRAWN: 'Search-FilterWithdrawn', - FILTER_WITHDRAWAL_TYPE: 'Search-FilterWithdrawalType', - FILTER_HAS: 'Search-FilterHas', - FILTER_IS: 'Search-FilterIs', ADVANCED_FILTERS_BUTTON: 'Search-AdvancedFiltersButton', COLUMNS_BUTTON: 'Search-ColumnsButton', SELECT_ALL_MATCHING_BUTTON: 'Search-SelectAllMatchingButton', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 8216ca68090c..42eae1002149 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -53,8 +53,6 @@ const SCREENS = { REPORT_VERIFY_ACCOUNT: 'Search_Report_Verify_Account', ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP', ADVANCED_FILTERS_TYPE_RHP: 'Search_Advanced_Filters_Type_RHP', - ADVANCED_FILTERS_GROUP_BY_RHP: 'Search_Advanced_Filters_GroupBy_RHP', - ADVANCED_FILTERS_VIEW_RHP: 'Search_Advanced_Filters_View_RHP', ADVANCED_FILTERS_STATUS_RHP: 'Search_Advanced_Filters_Status_RHP', ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP', ADVANCED_FILTERS_SUBMITTED_RHP: 'Search_Advanced_Filters_Submitted_RHP', @@ -65,7 +63,6 @@ const SCREENS = { ADVANCED_FILTERS_POSTED_RHP: 'Search_Advanced_Filters_Posted_RHP', ADVANCED_FILTERS_WITHDRAWN_RHP: 'Search_Advanced_Filters_Withdrawn_RHP', ADVANCED_FILTERS_CURRENCY_RHP: 'Search_Advanced_Filters_Currency_RHP', - ADVANCED_FILTERS_GROUP_CURRENCY_RHP: 'Search_Advanced_Filters_Group_Currency_RHP', ADVANCED_FILTERS_DESCRIPTION_RHP: 'Search_Advanced_Filters_Description_RHP', ADVANCED_FILTERS_MERCHANT_RHP: 'Search_Advanced_Filters_Merchant_RHP', ADVANCED_FILTERS_REPORT_ID_RHP: 'Search_Advanced_Filters_ReportID_RHP', @@ -81,7 +78,6 @@ const SCREENS = { ADVANCED_FILTERS_WITHDRAWAL_ID_RHP: 'Search_Advanced_Filters_Withdrawal_ID_RHP', ADVANCED_FILTERS_TAG_RHP: 'Search_Advanced_Filters_Tag_RHP', ADVANCED_FILTERS_HAS_RHP: 'Search_Advanced_Filters_Has_RHP', - ADVANCED_FILTERS_LIMIT_RHP: 'Search_Advanced_Filters_Limit_RHP', ADVANCED_FILTERS_FROM_RHP: 'Search_Advanced_Filters_From_RHP', ADVANCED_FILTERS_TO_RHP: 'Search_Advanced_Filters_To_RHP', ADVANCED_FILTERS_TITLE_RHP: 'Search_Advanced_Filters_Title_RHP', diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 0ff5931c3a88..963cbfc5eceb 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -495,7 +495,7 @@ function MoneyRequestReportTransactionList({ const groupByPopoverComponent = useCallback( (props: {closeOverlay: () => void}) => ( - + ), - [groupByOptions, reportLayoutGroupBy, styles, windowHeight, isInLandscapeMode], + [groupByOptions, reportLayoutGroupBy, styles, windowHeight, isSmallScreenWidth, isInLandscapeMode], ); const transactionListContent = ( diff --git a/src/components/Search/FilterDropdowns/DisplayPopup.tsx b/src/components/Search/FilterDropdowns/DisplayPopup.tsx index 085bcb9aa7a1..1c26236f5bdf 100644 --- a/src/components/Search/FilterDropdowns/DisplayPopup.tsx +++ b/src/components/Search/FilterDropdowns/DisplayPopup.tsx @@ -21,8 +21,10 @@ import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {SearchResults} from '@src/types/onyx'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; import GroupByPopup from './GroupByPopup'; +import GroupCurrencyPopup from './GroupCurrencyPopup'; import SingleSelectPopup from './SingleSelectPopup'; import SortByPopup from './SortByPopup'; +import SortOrderPopup from './SortOrderPopup'; import TextInputPopup from './TextInputPopup'; type DisplayPopupProps = { @@ -41,8 +43,10 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP const [selectedDisplayFilter, setSelectedDisplayFilter] = useState< | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.LIMIT | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY + | typeof CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY + | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER | null >(null); @@ -52,9 +56,6 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP const view = viewOptions.find((option) => option.value === queryJSON.view) ?? viewOptions.at(0) ?? null; const shouldShowColumnsButton = isLargeScreenWidth && (queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE || queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT); - const sortByValue = queryJSON.sortBy; - const groupByValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY]; - const viewValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW]; const limitValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_ROOT_KEYS.LIMIT]; if (!selectedDisplayFilter) { @@ -64,12 +65,18 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP const isExpenseType = queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE; const isTripType = queryJSON.type === CONST.SEARCH.DATA_TYPES.TRIP; + const sortByValue = queryJSON.sortBy; + const sortOrderValue = queryJSON.sortOrder; + const groupByValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY]; + const groupCurrencyValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY]; + const viewValue = searchAdvancedFilters[CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW]; + return ( setSelectedDisplayFilter(CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY)} sentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_SORT_BY} /> @@ -82,6 +89,15 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP sentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_GROUP_BY} /> )} + {!!groupBy && ( + setSelectedDisplayFilter(CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY)} + sentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_GROUP_CURRENCY} + /> + )} {isExpenseType && !!groupByValue && ( Navigation.setParams({q: queryString, rawQuery: undefined})); }; + const goBack = () => { + if (selectedDisplayFilter === CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER) { + setSelectedDisplayFilter(CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY); + return; + } + setSelectedDisplayFilter(null); + }; + const subtitle = { [CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY]: translate('search.display.sortBy'), + [CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER]: translate('search.display.sortOrder'), [CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY]: translate('search.display.groupBy'), + [CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY]: translate('common.groupCurrency'), [CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW]: translate('search.view.label'), [CONST.SEARCH.SYNTAX_ROOT_KEYS.LIMIT]: translate('search.display.limitResults'), }; - const subPopup = { - [CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY]: ( - setSelectedDisplayFilter(null)} - /> - ), - [CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY]: ( - setSelectedDisplayFilter(null)} - onChange={(item) => { - const newValue = item?.value; - if (!newValue) { - updateFilterForm({groupBy: undefined, groupCurrency: undefined}); - } else { - updateFilterForm({groupBy: newValue}); - } - }} - /> - ), - [CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW]: ( - setSelectedDisplayFilter(null)} - onChange={(item) => updateFilterForm({view: item?.value ?? CONST.SEARCH.VIEW.TABLE})} - /> - ), - [CONST.SEARCH.SYNTAX_ROOT_KEYS.LIMIT]: ( - setSelectedDisplayFilter(null)} - onChange={(value) => updateFilterForm({limit: value})} - /> - ), - }; + let subPopup: React.JSX.Element | null = null; + + switch (selectedDisplayFilter) { + case CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY: + subPopup = ( + setSelectedDisplayFilter(CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER)} + closeOverlay={closeOverlay} + /> + ); + break; + case CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER: + subPopup = ( + + ); + break; + case CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY: + subPopup = ( + { + const newValue = item?.value; + if (!newValue) { + updateFilterForm({groupBy: undefined, groupCurrency: undefined}); + } else { + updateFilterForm({groupBy: newValue}); + } + }} + /> + ); + break; + case CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY: + subPopup = ( + updateFilterForm({groupCurrency: item?.value})} + closeOverlay={closeOverlay} + /> + ); + break; + case CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW: + subPopup = ( + updateFilterForm({view: item?.value ?? CONST.SEARCH.VIEW.TABLE})} + /> + ); + break; + case CONST.SEARCH.SYNTAX_ROOT_KEYS.LIMIT: + subPopup = ( + updateFilterForm({limit: value})} + /> + ); + break; + default: + break; + } return ( @@ -199,9 +253,9 @@ function DisplayPopup({queryJSON, searchResults, closeOverlay, onSort}: DisplayP shouldDisplayHelpButton={false} style={[styles.h10, styles.pv1, styles.mb2]} subtitle={subtitle[selectedDisplayFilter]} - onBackButtonPress={() => setSelectedDisplayFilter(null)} + onBackButtonPress={goBack} /> - {subPopup[selectedDisplayFilter]} + {subPopup} ); } diff --git a/src/components/Search/FilterDropdowns/DropdownButton.tsx b/src/components/Search/FilterDropdowns/DropdownButton.tsx index b33429f991a9..4e835a205266 100644 --- a/src/components/Search/FilterDropdowns/DropdownButton.tsx +++ b/src/components/Search/FilterDropdowns/DropdownButton.tsx @@ -1,5 +1,5 @@ import {willAlertModalBecomeVisibleSelector} from '@selectors/Modal'; -import type {ReactNode} from 'react'; +import type {ReactNode, RefObject} from 'react'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; @@ -26,6 +26,8 @@ type PopoverComponentProps = { }; type DropdownButtonProps = WithSentryLabel & { + children?: (triggerRef: RefObject, onPress: () => void) => ReactNode; + /** The label to display on the select */ label: string; @@ -38,6 +40,8 @@ type DropdownButtonProps = WithSentryLabel & { /** The component to render in the popover */ PopoverComponent: (props: PopoverComponentProps) => ReactNode; + ButtonComponent?: React.ComponentType<{onPress: () => void; ref: RefObject}>; + /** Whether to use medium size button instead of small */ medium?: boolean; @@ -54,14 +58,24 @@ type DropdownButtonProps = WithSentryLabel & { wrapperStyle?: StyleProp; }; -const PADDING_MODAL = 8; - const ANCHOR_ORIGIN = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }; -function DropdownButton({label, value, viewportOffsetTop, PopoverComponent, medium = false, labelStyle, innerStyles, caretWrapperStyle, wrapperStyle, sentryLabel}: DropdownButtonProps) { +function DropdownButton({ + label, + value, + viewportOffsetTop, + PopoverComponent, + ButtonComponent, + medium = false, + labelStyle, + innerStyles, + caretWrapperStyle, + wrapperStyle, + sentryLabel, +}: DropdownButtonProps) { // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to distinguish RHL and narrow layout // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); @@ -100,7 +114,7 @@ function DropdownButton({label, value, viewportOffsetTop, PopoverComponent, medi */ const calculatePopoverPositionAndToggleOverlay = useCallback(() => { calculatePopoverPosition(anchorRef, ANCHOR_ORIGIN).then((pos) => { - setPopoverTriggerPosition({...pos, vertical: pos.vertical + PADDING_MODAL}); + setPopoverTriggerPosition({...pos, vertical: pos.vertical}); toggleOverlay(); }); }, [calculatePopoverPosition, toggleOverlay]); @@ -136,28 +150,35 @@ function DropdownButton({label, value, viewportOffsetTop, PopoverComponent, medi style={wrapperStyle} > {/* Dropdown Trigger */} - + + {buttonText} + + + + )} {/* Dropdown overlay */} section.data).length || 1; return ( section.data).length || 1, windowHeight, shouldUseNarrowLayout, isInLandscapeMode, true)]} + style={[styles.getCommonSelectionListPopoverHeight(itemCount, variables.optionRowHeight, windowHeight, shouldUseNarrowLayout, isInLandscapeMode, true)]} > ({label, loading, value, items, close ListItem={MultiSelectListItem} onSelectRow={updateSelectedItems} textInputOptions={textInputOptions} + style={{contentContainerStyle: [styles.pb0]}} /> )} diff --git a/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx b/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx index 8f4e44ad0e7d..5d3ff7964fad 100644 --- a/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/SingleSelectPopup.tsx @@ -145,7 +145,7 @@ function SingleSelectPopup({ ListItem={SingleSelectListItem} onSelectRow={updateSelectedItem} textInputOptions={textInputOptions} - style={selectionListStyle} + style={{contentContainerStyle: [styles.pb0], ...selectionListStyle}} shouldUpdateFocusedIndex={isSearchable} initiallyFocusedItemKey={isSearchable ? value?.value : undefined} shouldShowLoadingPlaceholder={!noResultsFound} diff --git a/src/components/Search/FilterDropdowns/SortByPopup.tsx b/src/components/Search/FilterDropdowns/SortByPopup.tsx index 9b82d6426a6a..7979f4519b0b 100644 --- a/src/components/Search/FilterDropdowns/SortByPopup.tsx +++ b/src/components/Search/FilterDropdowns/SortByPopup.tsx @@ -1,11 +1,12 @@ import React from 'react'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {useSearchActionsContext, useSearchStateContext} from '@components/Search/SearchContext'; import type {SearchColumnType, SearchGroupBy, SearchQueryJSON} from '@components/Search/types'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {close} from '@libs/actions/Modal'; import Navigation from '@libs/Navigation/Navigation'; @@ -23,13 +24,13 @@ type SortByPopupProps = { queryJSON: SearchQueryJSON; groupBy: SingleSelectItem | null; onSort: () => void; + onSortOrderPress: () => void; closeOverlay: () => void; }; -function SortByPopup({searchResults, queryJSON, groupBy, onSort, closeOverlay}: SortByPopupProps) { +function SortByPopup({searchResults, queryJSON, groupBy, onSort, onSortOrderPress, closeOverlay}: SortByPopupProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); const {accountID} = useCurrentUserPersonalDetails(); const {shouldUseLiveData} = useSearchStateContext(); const {clearSelectedTransactions} = useSearchActionsContext(); @@ -40,14 +41,11 @@ function SortByPopup({searchResults, queryJSON, groupBy, onSort, closeOverlay}: : getColumnsToShow({currentAccountID: accountID, data: searchResults.data, visibleColumns, type: searchDataType, groupBy: groupBy?.value}); const sortableColumns = getSortByOptions(currentColumns, translate); const sortBy = {text: translate(getSearchColumnTranslationKey(queryJSON.sortBy)), value: queryJSON.sortBy}; + const sortOrder = queryJSON.sortOrder; const onSortChange = (column: SearchColumnType) => { clearSelectedTransactions(); - const newQuery = buildSearchQueryString({ - ...queryJSON, - sortBy: column, - sortOrder: CONST.SEARCH.SORT_ORDER.DESC, - }); + const newQuery = buildSearchQueryString({...queryJSON, sortBy: column}); onSort(); // We want to explicitly clear stale rawQuery since it's only used for manually typed-in queries. close(() => { @@ -56,20 +54,28 @@ function SortByPopup({searchResults, queryJSON, groupBy, onSort, closeOverlay}: }; return ( - { - if (!item) { - return; - } - onSortChange(item.value); - }} - /> + <> + + + { + if (!item) { + return; + } + onSortChange(item.value); + }} + /> + ); } diff --git a/src/components/Search/FilterDropdowns/SortOrderPopup.tsx b/src/components/Search/FilterDropdowns/SortOrderPopup.tsx new file mode 100644 index 000000000000..83734a263380 --- /dev/null +++ b/src/components/Search/FilterDropdowns/SortOrderPopup.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import {useSearchActionsContext} from '@components/Search/SearchContext'; +import type {SearchQueryJSON, SortOrder} from '@components/Search/types'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {close} from '@libs/actions/Modal'; +import Navigation from '@libs/Navigation/Navigation'; +import {buildSearchQueryString} from '@libs/SearchQueryUtils'; +import {getSortOrderOptions} from '@libs/SearchUIUtils'; +import SingleSelectPopup from './SingleSelectPopup'; + +type SortOrderPopupProps = { + queryJSON: SearchQueryJSON; + onSort: () => void; + closeOverlay: () => void; +}; + +function SortOrderPopup({queryJSON, onSort, closeOverlay}: SortOrderPopupProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {clearSelectedTransactions} = useSearchActionsContext(); + + const onSortChange = (sortOrder: SortOrder) => { + clearSelectedTransactions(); + const newQuery = buildSearchQueryString({...queryJSON, sortOrder}); + onSort(); + // We want to explicitly clear stale rawQuery since it's only used for manually typed-in queries. + close(() => { + Navigation.setParams({q: newQuery, rawQuery: undefined}); + }); + }; + + const sortOrderOptions = getSortOrderOptions(translate); + const sortOrder = {text: translate(`search.filters.sortOrder.${queryJSON.sortOrder}`), value: queryJSON.sortOrder}; + + return ( + { + if (!item) { + return; + } + onSortChange(item.value); + }} + /> + ); +} + +export default SortOrderPopup; diff --git a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx index f220e9890b5a..e751d236cf06 100644 --- a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx @@ -14,6 +14,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import {getParticipantsOption} from '@libs/OptionsListUtils'; import type {OptionData} from '@libs/ReportUtils'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import BasePopup from './BasePopup'; @@ -176,7 +177,16 @@ function UserSelectPopup({value, label, closeOverlay, onChange, isSearchable}: U onApply={applyChanges} resetSentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_USER} applySentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_USER} - style={[styles.getUserSelectionListPopoverHeight(listData.length || 1, windowHeight, shouldUseNarrowLayout, isInLandscapeMode, shouldShowSearchInput)]} + style={[ + styles.getCommonSelectionListPopoverHeight( + listData.length || 1, + variables.optionRowHeightCompact, + windowHeight, + shouldUseNarrowLayout, + isInLandscapeMode, + shouldShowSearchInput, + ), + ]} > ); diff --git a/src/components/Search/FilterDropdowns/WorkspaceSelectPopup.tsx b/src/components/Search/FilterDropdowns/WorkspaceSelectPopup.tsx index 4f8be0b07f51..e2afd79b33a6 100644 --- a/src/components/Search/FilterDropdowns/WorkspaceSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/WorkspaceSelectPopup.tsx @@ -6,13 +6,13 @@ import useOnyx from '@hooks/useOnyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {Icon} from '@src/types/onyx/OnyxCommon'; -import type {PopoverComponentProps} from './DropdownButton'; import type {MultiSelectItem} from './MultiSelectPopup'; import MultiSelectPopup from './MultiSelectPopup'; -type WorkspaceSelectPopupProps = PopoverComponentProps & { - updateFilterForm: (values: Partial) => void; +type WorkspaceSelectPopupProps = { policyIDQuery: string[] | undefined; + updateFilterForm: (values: Partial) => void; + closeOverlay: () => void; }; function filterPolicyIDSelector(searchAdvancedFiltersForm: OnyxEntry) { diff --git a/src/components/Search/SearchAutocompleteInput.tsx b/src/components/Search/SearchAutocompleteInput.tsx index 9b097fb86a0f..df1785d31675 100644 --- a/src/components/Search/SearchAutocompleteInput.tsx +++ b/src/components/Search/SearchAutocompleteInput.tsx @@ -2,7 +2,7 @@ import passthroughPolicyTagListSelector from '@selectors/PolicyTagList'; import type {ForwardedRef} from 'react'; import React, {useEffect, useRef} from 'react'; -import type {StyleProp, TextInputProps, ViewStyle} from 'react-native'; +import type {StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import Animated, {interpolateColor, useAnimatedStyle, useSharedValue} from 'react-native-reanimated'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -60,6 +60,12 @@ type SearchAutocompleteInputProps = { /** Any additional styles to apply to text input along with FormHelperMessage */ outerWrapperStyle?: StyleProp; + inputStyle?: StyleProp; + + inputContainerStyle?: StyleProp; + + touchableInputWrapperStyle?: StyleProp; + /** Whether the search reports API call is running */ isSearchingForReports?: boolean; @@ -88,6 +94,9 @@ function SearchAutocompleteInput({ wrapperStyle, wrapperFocusedStyle = {}, outerWrapperStyle, + inputStyle, + inputContainerStyle, + touchableInputWrapperStyle, isSearchingForReports, selection, substitutionMap, @@ -193,63 +202,62 @@ function SearchAutocompleteInput({ return ( - - - { - onFocus?.(); - focusedSharedValue.set(true); - }} - onBlur={() => { - focusedSharedValue.set(false); - onBlur?.(); - }} - onKeyPress={onKeyPress} - isLoading={isSearchingForReports} - ref={(element) => { - if (!ref) { - return; - } - - inputRef.current = element as AnimatedTextInputRef; - - if (typeof ref === 'function') { - ref(element); - return; - } - - // eslint-disable-next-line no-param-reassign - ref.current = element; - }} - type="markdown" - multiline={false} - parser={parser} - selection={selection} - shouldShowClearButton={!!value && !isSearchingForReports} - shouldHideClearButton={false} - onClearInput={clearInput} - /> - + + { + onFocus?.(); + focusedSharedValue.set(true); + }} + onBlur={() => { + focusedSharedValue.set(false); + onBlur?.(); + }} + onKeyPress={onKeyPress} + isLoading={isSearchingForReports} + ref={(element) => { + if (!ref) { + return; + } + + inputRef.current = element as AnimatedTextInputRef; + + if (typeof ref === 'function') { + ref(element); + return; + } + + // eslint-disable-next-line no-param-reassign + ref.current = element; + }} + type="markdown" + multiline={false} + parser={parser} + selection={selection} + shouldShowClearButton={!!value && !isSearchingForReports} + shouldHideClearButton={false} + onClearInput={clearInput} + /> {}, removeTransaction: () => {}, clearSelectedTransactions: () => {}, - setShouldShowActionsBarLoading: () => {}, + setShouldShowFiltersBarLoading: () => {}, setShouldShowSelectAllMatchingItems: () => {}, selectAllMatchingItems: () => {}, setShouldResetSearchQuery: () => {}, @@ -103,7 +103,7 @@ function SearchContextProvider({children}: SearchContextProps) { const areTransactionsEmpty = useRef(true); const [lastSearchType, setLastSearchType] = useState(); const [areAllMatchingItemsSelected, selectAllMatchingItems] = useState(false); - const [shouldShowActionsBarLoading, setShouldShowActionsBarLoading] = useState(false); + const [shouldShowFiltersBarLoading, setShouldShowFiltersBarLoading] = useState(false); const [shouldShowSelectAllMatchingItems, setShouldShowSelectAllMatchingItems] = useState(false); const [searchContextData, setSearchContextData] = useState({...defaultSearchContextData}); @@ -330,7 +330,7 @@ function SearchContextProvider({children}: SearchContextProps) { currentSimilarSearchHash, currentSearchResults, shouldUseLiveData, - shouldShowActionsBarLoading, + shouldShowFiltersBarLoading, lastSearchType, shouldShowSelectAllMatchingItems, areAllMatchingItemsSelected, @@ -344,7 +344,7 @@ function SearchContextProvider({children}: SearchContextProps) { currentSimilarSearchHash, currentSearchResults, shouldUseLiveData, - shouldShowActionsBarLoading, + shouldShowFiltersBarLoading, lastSearchType, shouldShowSelectAllMatchingItems, areAllMatchingItemsSelected, @@ -358,13 +358,13 @@ function SearchContextProvider({children}: SearchContextProps) { setSelectedTransactions, setCurrentSelectedTransactionReportID, clearSelectedTransactions, - setShouldShowActionsBarLoading, + setShouldShowFiltersBarLoading, setLastSearchType, setShouldShowSelectAllMatchingItems, selectAllMatchingItems, setShouldResetSearchQuery, }), - // setShouldShowActionsBarLoading, setLastSearchType, setShouldShowSelectAllMatchingItems, + // shouldShowFiltersBarLoading, setLastSearchType, setShouldShowSelectAllMatchingItems, // and selectAllMatchingItems are stable useState setters — excluded from deps intentionally. // setCurrentSelectedTransactionReportID only uses setSearchContextData (stable setter). [removeTransaction, setSelectedTransactions, clearSelectedTransactions, setShouldResetSearchQuery], diff --git a/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx b/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx index f0ff7e5c994d..8e11164b2582 100644 --- a/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx +++ b/src/components/Search/SearchPageHeader/SearchActionsBarCreateButton.tsx @@ -227,7 +227,7 @@ function SearchActionsBarCreateButton() { ); return ( - + ; - onSearchButtonPress: () => void; onSort: () => void; }; -// NOTE: This is intentionally unused for now. It will be wired up in https://github.com/Expensify/App/issues/84876 -function SearchActionsBarNarrow({queryJSON, isMobileSelectionModeEnabled, isSearchInputVisible, searchResults, onSearchButtonPress, onSort}: SearchActionsBarNarrowProps) { - const {hasErrors, shouldShowActionsBarLoading, shouldShowSelectedDropdown, styles} = useSearchActionsBar(queryJSON, isMobileSelectionModeEnabled); - const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']); - - if (hasErrors) { - return null; - } - - if (shouldShowActionsBarLoading) { - const skeletonReasonAttributes: SkeletonSpanReasonAttributes = { - context: 'SearchActionsBarNarrow', - shouldShowActionsBarLoading, - }; - return ( - - ); - } +function SearchActionsBarNarrow({queryJSON, searchResults, onSort}: SearchActionsBarNarrowProps) { + const styles = useThemeStyles(); return ( - - {shouldShowSelectedDropdown ? ( - - ) : ( - <> - - {!isSearchInputVisible && ( - - ); -} - -/** - * In the submit-and-navigate flow we only ever land on `type:expense` or `type:invoice` - * with default status and no extra filters, so the chips are mostly hardcoded. - * The only conditional chip is "Workspaces" (shown when the user has >1 workspace), - * resolved via a cheap boolean Onyx selector. - */ -function StaticFiltersBar({queryJSON}: {queryJSON: SearchQueryJSON}) { - const styles = useThemeStyles(); - const theme = useTheme(); - const {translate} = useLocalize(); - const expensifyIcons = useMemoizedLazyExpensifyIcons(['Filter'] as const); - const [policyInfo] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: staticPolicyInfoSelector}); - const hasMultipleWorkspaces = policyInfo?.hasMultipleWorkspaces ?? false; - - const typeLabel = queryJSON.type === CONST.SEARCH.DATA_TYPES.INVOICE ? translate('common.invoice') : translate('common.expense'); - - const chips = useMemo( - () => [ - {key: 'type', label: `${translate('common.type')}: ${typeLabel}`}, - {key: 'status', label: translate('common.status')}, - {key: 'date', label: translate('common.date')}, - {key: 'from', label: translate('common.from')}, - ...(hasMultipleWorkspaces ? [{key: 'workspace', label: translate('workspace.common.workspace')}] : []), - ], - [translate, typeLabel, hasMultipleWorkspaces], - ); - - return ( - - item.key} - renderItem={({item}) => } - ListFooterComponent={ -