diff --git a/Mobile-Expensify b/Mobile-Expensify index 0711f7b17236..bb40f247cc91 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 0711f7b172369a07844ae35c4151a8c4472c32f9 +Subproject commit bb40f247cc916810d5a922919d086064d72efd04 diff --git a/android/app/build.gradle b/android/app/build.gradle index 937df4d73ee3..115133f3d9b6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -114,8 +114,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009028102 - versionName "9.2.81-2" + versionCode 1009028103 + versionName "9.2.81-3" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 2843830f9994..497864c84220 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -44,7 +44,7 @@ CFBundleVersion - 9.2.81.2 + 9.2.81.3 FullStory OrgId diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 31751c9c6199..37893af596ed 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.81 CFBundleVersion - 9.2.81.2 + 9.2.81.3 NSExtension NSExtensionPointIdentifier diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist index b8fbb0d452c0..1584c8ec31eb 100644 --- a/ios/ShareViewController/Info.plist +++ b/ios/ShareViewController/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.81 CFBundleVersion - 9.2.81.2 + 9.2.81.3 NSExtension NSExtensionAttributes diff --git a/package-lock.json b/package-lock.json index 9691e59c7b19..7e0e11e94207 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.2.81-2", + "version": "9.2.81-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.2.81-2", + "version": "9.2.81-3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 0197ab3a3547..79afaa855c7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.2.81-2", + "version": "9.2.81-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 48819c93f275..0826609cdbd7 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -6692,6 +6692,7 @@ const CONST = { APPROVED: this.TABLE_COLUMNS.APPROVED, POSTED: this.TABLE_COLUMNS.POSTED, MERCHANT: this.TABLE_COLUMNS.MERCHANT, + DESCRIPTION: this.TABLE_COLUMNS.DESCRIPTION, FROM: this.TABLE_COLUMNS.FROM, TO: this.TABLE_COLUMNS.TO, CATEGORY: this.TABLE_COLUMNS.CATEGORY, diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 3f610c2c85f3..5b220ae5fd02 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -5,7 +5,6 @@ import {View} from 'react-native'; import type {TupleToUnion} from 'type-fest'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import Checkbox from '@components/Checkbox'; -import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -65,7 +64,6 @@ import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import {columnsSelector} from '@src/selectors/AdvancedSearchFiltersForm'; import type * as OnyxTypes from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import MoneyRequestReportGroupHeader from './MoneyRequestReportGroupHeader'; @@ -165,7 +163,7 @@ function MoneyRequestReportTransactionList({ useCopySelectionHelper(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const expensifyIcons = useMemoizedLazyExpensifyIcons(['Location']); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['Location', 'CheckSquare']); const {translate, localeCompare} = useLocalize(); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth, isMediumScreenWidth} = useResponsiveLayout(); @@ -184,7 +182,6 @@ function MoneyRequestReportTransactionList({ const shouldShowAddExpenseButton = canAddTransaction(report, isReportArchived) && isCurrentUserSubmitter(report); const [lastDistanceExpenseType] = useOnyx(ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE, {canBeMissing: true}); const [reportLayoutGroupBy] = useOnyx(ONYXKEYS.NVP_REPORT_LAYOUT_GROUP_BY, {canBeMissing: true}); - const [visibleColumns] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {canBeMissing: true, selector: columnsSelector}); const shouldShowGroupedTransactions = isExpenseReport(report) && !isIOUReport(report); @@ -268,9 +265,9 @@ function MoneyRequestReportTransactionList({ }, [newTransactions, sortBy, sortOrder, transactions, localeCompare, report]); const columnsToShow = useMemo(() => { - const columns = getColumnsToShow(currentUserDetails?.accountID, transactions, visibleColumns, true); + const columns = getColumnsToShow(currentUserDetails?.accountID, transactions, [], true); return (Object.keys(columns) as SearchColumnType[]).filter((column) => columns[column]); - }, [transactions, currentUserDetails?.accountID, visibleColumns]); + }, [transactions, currentUserDetails?.accountID]); const currentGroupBy = getReportLayoutGroupBy(reportLayoutGroupBy); @@ -638,7 +635,7 @@ function MoneyRequestReportTransactionList({ > { if (!isMobileSelectionModeEnabled) { turnOnMobileSelectionMode(); diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index db4dfa18f065..5978fb4c9b69 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -3,13 +3,13 @@ import {isUserValidatedSelector} from '@selectors/Account'; import {accountIDSelector} from '@selectors/Session'; import {tierNameSelector} from '@selectors/UserWallet'; import type {FlashListProps, FlashListRef, ViewToken} from '@shopify/flash-list'; -import React, {useCallback, useContext, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; -import type {NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import type {NativeScrollEvent, NativeSyntheticEvent, ScrollView as RNScrollView, StyleProp, ViewStyle} from 'react-native'; import Animated, {Easing, FadeOutUp, LinearTransition} from 'react-native-reanimated'; import Checkbox from '@components/Checkbox'; -import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; @@ -31,6 +31,7 @@ import type { } from '@components/SelectionListWithSections/types'; import Text from '@components/Text'; import useKeyboardState from '@hooks/useKeyboardState'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -51,6 +52,9 @@ import BaseSearchList from './BaseSearchList'; const easing = Easing.bezier(0.76, 0.0, 0.24, 1.0); +// Keep a ref to the horizontal scroll offset so we can restore it if users change the search query +let savedHorizontalScrollOffset = 0; + type SearchListItem = TransactionListItemType | TransactionGroupListItemType | ReportActionListItemType | TaskListItemType; type SearchListItemComponentType = typeof TransactionListItem | typeof ChatListItem | typeof TransactionGroupListItem | typeof TaskListItem; @@ -175,6 +179,7 @@ function SearchList({ ref, }: SearchListProps) { const styles = useThemeStyles(); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['CheckSquare']); const {hash, groupBy, type} = queryJSON; const flattenedItems = useMemo(() => { @@ -233,6 +238,20 @@ function SearchList({ const minTableWidth = getTableMinWidth(columns); const shouldScrollHorizontally = !!SearchTableHeader && minTableWidth > windowWidth; + const horizontalScrollViewRef = useRef(null); + + const handleHorizontalScroll = useCallback((event: NativeSyntheticEvent) => { + savedHorizontalScrollOffset = event.nativeEvent.contentOffset.x; + }, []); + + // Restore horizontal scroll position synchronously before paint using useLayoutEffect to avoid a visible shift on the table + useLayoutEffect(() => { + if (!shouldScrollHorizontally || savedHorizontalScrollOffset <= 0) { + return; + } + horizontalScrollViewRef.current?.scrollTo({x: savedHorizontalScrollOffset, animated: false}); + }, [data, shouldScrollHorizontally]); + const handleLongPressRow = useCallback( (item: SearchListItem, itemTransactions?: TransactionListItemType[]) => { const currentRoute = navigationRef.current?.getCurrentRoute(); @@ -442,7 +461,7 @@ function SearchList({ > @@ -452,10 +471,14 @@ function SearchList({ if (shouldScrollHorizontally) { return ( {content} diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx index cc8e17439330..460cad4c99c1 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx @@ -1,8 +1,7 @@ -import {useIsFocused} from '@react-navigation/native'; import {isUserValidatedSelector} from '@selectors/Account'; import {emailSelector} from '@selectors/Session'; import {searchResultsErrorSelector} from '@selectors/Snapshot'; -import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useContext, useMemo, useRef} from 'react'; import type {ReactNode} from 'react'; import {FlatList, View} from 'react-native'; import Button from '@components/Button'; @@ -36,14 +35,14 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceList from '@hooks/useWorkspaceList'; import {close} from '@libs/actions/Modal'; -import {handleBulkPayItemSelected, updateAdvancedFilters} from '@libs/actions/Search'; +import {handleBulkPayItemSelected} from '@libs/actions/Search'; import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; -import {getActiveAdminWorkspaces, getAllTaxRates, isPaidGroupPolicy} from '@libs/PolicyUtils'; +import {getActiveAdminWorkspaces, isPaidGroupPolicy} from '@libs/PolicyUtils'; import {isExpenseReport} from '@libs/ReportUtils'; -import {buildFilterFormValuesFromQuery, buildQueryStringFromFilterFormValues, isFilterSupported, isSearchDatePreset} from '@libs/SearchQueryUtils'; +import {buildQueryStringFromFilterFormValues, isFilterSupported, isSearchDatePreset} from '@libs/SearchQueryUtils'; import {getDatePresets, getFeedOptions, getGroupByOptions, getGroupCurrencyOptions, getHasOptions, getStatusOptions, getTypeOptions, getWithdrawalTypeOptions} from '@libs/SearchUIUtils'; import shouldAdjustScroll from '@libs/shouldAdjustScroll'; import CONST from '@src/CONST'; @@ -84,10 +83,10 @@ function SearchFiltersBar({ confirmPayment, latestBankItems, }: SearchFiltersBarProps) { - const isFocused = useIsFocused(); const scrollRef = useRef>(null); const currentPolicy = usePolicy(currentSelectedPolicyID); const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector, canBeMissing: true}); + const [filterFormValues = getEmptyObject>()] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {canBeMissing: true}); // type, groupBy and status values are not guaranteed to respect the ts type as they come from user input const {hash, type: unsafeType, groupBy: unsafeGroupBy, status: unsafeStatus, flatFilters} = queryJSON; const [selectedIOUReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${currentSelectedReportID}`, {canBeMissing: true}); @@ -104,19 +103,14 @@ function SearchFiltersBar({ const [email] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true, selector: emailSelector}); const [userCardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true}); - const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); - const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); - const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {canBeMissing: true}); - const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, {canBeMissing: true}); const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true}); const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true}); + const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); const [searchResultsErrors] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true, selector: searchResultsErrorSelector}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Filter', 'Columns']); - const taxRates = getAllTaxRates(allPolicies); - // Get workspace data for the filter const {sections: workspaces, shouldShowSearchInput: shouldShowWorkspaceSearchInput} = useWorkspaceList({ policies: allPolicies, @@ -148,10 +142,6 @@ function SearchFiltersBar({ return policies.some((policy) => policy.outputCurrency !== outputCurrency); }, [allPolicies]); - const filterFormValues = useMemo(() => { - return buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, allCards, reports, taxRates); - }, [allCards, currencyList, personalDetails, policyCategories, policyTagsLists, queryJSON, reports, taxRates]); - // Get selected workspace options from filterFormValues or queryJSON const selectedWorkspaceOptions = useMemo(() => { const policyIDs = filterFormValues.policyID ?? queryJSON.policyID; @@ -321,28 +311,13 @@ function SearchFiltersBar({ ); const openAdvancedFilters = useCallback(() => { - updateAdvancedFilters(filterFormValues, true); Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS.getRoute()); - }, [filterFormValues]); + }, []); const openSearchColumns = () => { Navigation.navigate(ROUTES.SEARCH_COLUMNS); }; - const isFormInitializedRef = useRef(false); - - useEffect(() => { - if (isFormInitializedRef.current) { - return; - } - if (filterFormValues && Object.keys(filterFormValues).length > 0) { - if (isFocused) { - updateAdvancedFilters(filterFormValues, true); - } - isFormInitializedRef.current = true; - } - }, [queryJSON, filterFormValues, isFocused]); - const typeComponent = useCallback( ({closeOverlay}: PopoverComponentProps) => { return ( diff --git a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx index 100aec4ad035..885cc52a98aa 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeaderInput.tsx @@ -25,7 +25,6 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {navigateToAndOpenReport} from '@libs/actions/Report'; -import {clearAllFilters} from '@libs/actions/Search'; import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; @@ -214,7 +213,6 @@ function SearchPageHeaderInput({queryJSON, searchRouterListVisible, hideSearchRo hideSearchRouterList?.(); setIsAutocompleteListVisible(false); if (updatedQuery !== originalInputQuery) { - clearAllFilters(); setTextInputValue(''); setAutocompleteQueryValue(''); } diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx index 90f6036fc8da..28f7e7317179 100644 --- a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx @@ -4,6 +4,7 @@ import ActivityIndicator from '@components/ActivityIndicator'; import Button from '@components/Button'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {PressableWithFeedback} from '@components/Pressable'; +import ScrollView from '@components/ScrollView'; import type {SearchColumnType} from '@components/Search/types'; import SearchTableHeader, {getExpenseHeaders} from '@components/SelectionListWithSections/SearchTableHeader'; import type {ListItem, TransactionGroupListExpandedProps, TransactionListItemType} from '@components/SelectionListWithSections/types'; @@ -16,9 +17,10 @@ import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import {getReportIDForTransaction} from '@libs/MoneyRequestReportUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {createAndOpenSearchTransactionThread, getColumnsToShow} from '@libs/SearchUIUtils'; +import {createAndOpenSearchTransactionThread, getColumnsToShow, getTableMinWidth} from '@libs/SearchUIUtils'; import {getTransactionViolations} from '@libs/TransactionUtils'; import {setActiveTransactionIDs} from '@userActions/TransactionThreadNavigation'; import CONST from '@src/CONST'; @@ -50,6 +52,7 @@ function TransactionGroupListExpanded({ }: TransactionGroupListExpandedProps) { const theme = useTheme(); const styles = useThemeStyles(); + const {windowWidth} = useWindowDimensions(); const currentUserDetails = useCurrentUserPersonalDetails(); const {translate} = useLocalize(); const [isMobileSelectionModeEnabled] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); @@ -174,8 +177,11 @@ function TransactionGroupListExpanded({ openReportInRHP(transaction); }; - return ( - <> + const minTableWidth = getTableMinWidth(columns ?? []); + const shouldScrollHorizontally = isLargeScreenWidth && minTableWidth > windowWidth; + + const content = ( + {isLargeScreenWidth && ( ({ /> )} - + + ); + + return shouldScrollHorizontally ? ( + + {content} + + ) : ( + content ); } diff --git a/src/hooks/useDeleteSavedSearch.tsx b/src/hooks/useDeleteSavedSearch.tsx index f92a157be409..f8c8d1753784 100644 --- a/src/hooks/useDeleteSavedSearch.tsx +++ b/src/hooks/useDeleteSavedSearch.tsx @@ -1,7 +1,7 @@ import React, {useState} from 'react'; import ConfirmModal from '@components/ConfirmModal'; import {useSearchContext} from '@components/Search/SearchContext'; -import {clearAdvancedFilters, deleteSavedSearch} from '@libs/actions/Search'; +import {deleteSavedSearch} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import {buildCannedSearchQuery} from '@libs/SearchQueryUtils'; import ROUTES from '@src/ROUTES'; @@ -23,7 +23,6 @@ export default function useDeleteSavedSearch() { setIsDeleteModalVisible(false); if (hashToDelete === currentSearchHash) { - clearAdvancedFilters(); Navigation.navigate( ROUTES.SEARCH_ROOT.getRoute({ query: buildCannedSearchQuery(), diff --git a/src/hooks/useSuggestedSearchDefaultNavigation.ts b/src/hooks/useSuggestedSearchDefaultNavigation.ts index b0a33365f960..b396e4ee0a68 100644 --- a/src/hooks/useSuggestedSearchDefaultNavigation.ts +++ b/src/hooks/useSuggestedSearchDefaultNavigation.ts @@ -1,5 +1,4 @@ import {useEffect, useRef} from 'react'; -import {clearAllFilters} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import type {SearchTypeMenuItem} from '@libs/SearchUIUtils'; import {getDefaultActionableSearchMenuItem} from '@libs/SearchUIUtils'; @@ -44,7 +43,6 @@ function useSuggestedSearchDefaultNavigation({ return; } - clearAllFilters(); clearSelectedTransactions(); Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: defaultMenuItem.searchQuery})); }, [shouldShowSkeleton, flattenedMenuItems, similarSearchHash, clearSelectedTransactions, shouldSkipNavigation]); diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 65e7905174cb..453b2f378e4a 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -1208,6 +1208,10 @@ function buildUserReadableQueryString( continue; } + if (rawFilter.key === CONST.SEARCH.SYNTAX_ROOT_KEYS.COLUMNS) { + continue; + } + if (rawFilter.isDefault) { const defaultSegment = formatDefaultRawFilterSegment(rawFilter, policies, reports); if (defaultSegment) { diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 2ce5e4adb71c..43c42b7529f3 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -934,7 +934,8 @@ function shouldShowYear( for (const key of Object.keys(data)) { if (!checkOnlyReports && isTransactionEntry(key)) { const item = data[key]; - if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { + const transactionCreated = getTransactionCreatedDate(item); + if (transactionCreated && DateUtils.doesDateBelongToAPastYear(transactionCreated)) { result.shouldShowYearCreated = true; } const report = data[`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`]; @@ -2027,12 +2028,8 @@ function getSortedTransactionData( const aReport = a.report; const bReport = b.report; - if (!aReport || !bReport) { - return 0; - } - - const aValue = getReportStatusTranslation({stateNum: aReport.stateNum, statusNum: aReport.statusNum, translate}); - const bValue = getReportStatusTranslation({stateNum: bReport.stateNum, statusNum: bReport.statusNum, translate}); + const aValue = getReportStatusTranslation({stateNum: aReport?.stateNum, statusNum: aReport?.statusNum, translate}); + const bValue = getReportStatusTranslation({stateNum: bReport?.stateNum, statusNum: bReport?.statusNum, translate}); return compareValues(aValue, bValue, sortOrder, sortBy, localeCompare); }); } @@ -2236,6 +2233,8 @@ function getSearchColumnTranslationKey(columnId: SearchCustomColumnIds): Transla return 'search.filters.posted'; case CONST.SEARCH.TABLE_COLUMNS.MERCHANT: return 'common.merchant'; + case CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION: + return 'common.description'; case CONST.SEARCH.TABLE_COLUMNS.FROM: return 'common.from'; case CONST.SEARCH.TABLE_COLUMNS.TO: @@ -2643,7 +2642,10 @@ function getColumnsToShow( }; // If there are no visible columns, everything should be visible - if (!visibleColumns.length) { + const filteredVisibleColumns = visibleColumns.filter((column) => + Object.values(CONST.SEARCH.CUSTOM_COLUMNS.EXPENSE_REPORT).includes(column as ValueOf), + ); + if (!filteredVisibleColumns.length) { return reportColumns; } @@ -2767,7 +2769,10 @@ function getColumnsToShow( // If the user has set custom columns for the search, we need to respect their preference, and only show // them what they want to see - if (!arraysEqual(Object.values(CONST.SEARCH.DEFAULT_COLUMNS.EXPENSE), visibleColumns) && visibleColumns.length > 0) { + const filteredVisibleColumns = visibleColumns.filter((column) => + Object.values(CONST.SEARCH.CUSTOM_COLUMNS.EXPENSE).includes(column as ValueOf), + ); + if (!arraysEqual(Object.values(CONST.SEARCH.DEFAULT_COLUMNS.EXPENSE), filteredVisibleColumns) && filteredVisibleColumns.length > 0) { const requiredColumns = new Set([CONST.SEARCH.TABLE_COLUMNS.AVATAR, CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT, CONST.SEARCH.TABLE_COLUMNS.TYPE]); for (const column of Object.keys(columns) as SearchCustomColumnIds[]) { diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 5fcd61753af9..028b0e89f970 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -958,13 +958,6 @@ function updateAdvancedFilters(values: Nullable> = {}; for (const key of Object.values(FILTER_KEYS)) { @@ -1216,7 +1209,6 @@ export { queueExportSearchItemsToCSV, queueExportSearchWithTemplate, updateAdvancedFilters, - clearAllFilters, clearAdvancedFilters, deleteSavedSearch, payMoneyRequestOnSearch, diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index ba898ab8ac14..91ec29207a6c 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -20,14 +20,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; import type {WorkspaceListItem} from '@hooks/useWorkspaceList'; import useWorkspaceList from '@hooks/useWorkspaceList'; -import {clearAllFilters, saveSearch} from '@libs/actions/Search'; +import {saveSearch} from '@libs/actions/Search'; import {createCardFeedKey, getCardFeedKey, getCardFeedNamesWithType, getWorkspaceCardFeedKey} from '@libs/CardFeedUtils'; import {getCardDescription, mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; import {convertToDisplayStringWithoutCurrency} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import {createDisplayName} from '@libs/PersonalDetailsUtils'; import {getAllTaxRates, getCleanedTagName} from '@libs/PolicyUtils'; -import {getReportName} from '@libs/ReportUtils'; +import {computeReportName} from '@libs/ReportNameUtils'; import {buildCannedSearchQuery, buildQueryStringFromFilterFormValues, buildSearchQueryJSON, isCannedSearchQuery, isSearchDatePreset, sortOptionsWithEmptyValue} from '@libs/SearchQueryUtils'; import {getExpenseTypeTranslationKey, getStatusOptions} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; @@ -534,7 +534,7 @@ function getFilterExpenseDisplayTitle(filters: Partial, _: LocaleContextProps['translate'], reports?: OnyxCollection) { return filters.in - ?.map((id) => getReportName(reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`])) + ?.map((id) => computeReportName(reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`], reports)) ?.filter(Boolean) ?.join(', '); } @@ -573,7 +573,6 @@ function AdvancedSearchFilters() { const queryJSON = useMemo(() => buildSearchQueryJSON(queryString || buildCannedSearchQuery()), [queryString]); const applyFiltersAndNavigate = () => { - clearAllFilters(); Navigation.navigate( ROUTES.SEARCH_ROOT.getRoute({ query: queryString, diff --git a/src/pages/Search/SearchAdvancedFiltersPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage.tsx index 128476fb77fb..a0854153bd55 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage.tsx @@ -24,6 +24,10 @@ function SearchAdvancedFiltersPage() { return false; } + if (key === CONST.SEARCH.SYNTAX_ROOT_KEYS.COLUMNS) { + return false; + } + if (key === CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE) { return value !== CONST.SEARCH.DATA_TYPES.EXPENSE; } diff --git a/src/pages/Search/SearchColumnsPage.tsx b/src/pages/Search/SearchColumnsPage.tsx index 95774157ec52..1e11a5021b99 100644 --- a/src/pages/Search/SearchColumnsPage.tsx +++ b/src/pages/Search/SearchColumnsPage.tsx @@ -57,7 +57,7 @@ function SearchColumnsPage() { const sortedDefaultColumns = [...defaultCustomColumns].sort(); const sortedSelectedColumnIds = [...selectedColumnIds].sort(); - const shouldShowResetColumns = !arraysEqual(sortedSelectedColumnIds, sortedDefaultColumns); + const isDefaultColumns = arraysEqual(sortedSelectedColumnIds, sortedDefaultColumns); const onSelectItem = (item: ListItem) => { const updatedColumnId = item.keyForList as SearchCustomColumnIds; @@ -78,7 +78,7 @@ function SearchColumnsPage() { return; } - const updatedAdvancedFilters: Partial = {...searchAdvancedFiltersForm, columns: selectedColumnIds}; + const updatedAdvancedFilters: Partial = {...searchAdvancedFiltersForm, columns: isDefaultColumns ? undefined : selectedColumnIds}; const queryString = buildQueryStringFromFilterFormValues(updatedAdvancedFilters); Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: queryString}), {forceReplace: true}); @@ -92,7 +92,7 @@ function SearchColumnsPage() { includeSafeAreaPaddingBottom > - {shouldShowResetColumns && {translate('search.resetColumns')}} + {!isDefaultColumns && {translate('search.resetColumns')}} ()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const [newReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${newReportID}`, {canBeMissing: true}); const [newParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${newReport?.parentReportID}`, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); @@ -114,6 +126,7 @@ function SearchPage({route}: SearchPageProps) { const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES, {canBeMissing: true}); const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS, {canBeMissing: true}); + const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false); @@ -125,8 +138,10 @@ function SearchPage({route}: SearchPageProps) { const [rejectModalAction, setRejectModalAction] = useState | null>(null); + const [dismissedRejectUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_REJECT_USE_EXPLANATION, {canBeMissing: true}); const [dismissedHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {canBeMissing: true}); + const queryJSON = useMemo(() => buildSearchQueryJSON(route.params.q, route.params.rawQuery), [route.params.q, route.params.rawQuery]); const {saveScrollOffset} = useContext(ScrollOffsetContext); const activeAdminPolicies = getActiveAdminWorkspaces(policies, currentUserPersonalDetails?.accountID.toString()).sort((a, b) => localeCompare(a.name || '', b.name || '')); @@ -153,6 +168,8 @@ function SearchPage({route}: SearchPageProps) { const selectedReportIDs = Object.values(selectedReports).map((report) => report.reportID); const isCurrencySupportedBulkWallet = isCurrencySupportWalletBulkPay(selectedReports, selectedTransactions); const hasOnlyPersonalPolicies = useMemo(() => hasOnlyPersonalPoliciesUtil(policies), [policies]); + const taxRates = getAllTaxRates(policies); + const allCards = mergeCardListWithWorkspaceFeeds(workspaceCardFeeds ?? CONST.EMPTY_OBJECT, userCardList); // Collate a list of policyIDs from the selected transactions const selectedPolicyIDs = useMemo( @@ -177,6 +194,15 @@ function SearchPage({route}: SearchPageProps) { formattedAmount: totalFormattedAmount, }); + const formValues = queryJSON + ? buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, allCards, allReports, taxRates) + : getEmptyObject>(); + + // Sync the advanced filters form with the current query when it changes + useEffect(() => { + updateAdvancedFilters(formValues, true); + }, [formValues]); + useEffect(() => { confirmReadyToOpenApp(); }, []); diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 45d41fa31064..db4a07a63b74 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -22,7 +22,6 @@ import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections'; import useSingleExecution from '@hooks/useSingleExecution'; import useSuggestedSearchDefaultNavigation from '@hooks/useSuggestedSearchDefaultNavigation'; import useThemeStyles from '@hooks/useThemeStyles'; -import {clearAllFilters} from '@libs/actions/Search'; import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; @@ -244,7 +243,6 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { const focused = activeItemIndex === flattenedIndex; const onPress = singleExecution(() => { - clearAllFilters(); clearSelectedTransactions(); Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: item.searchQuery})); }); diff --git a/tests/unit/TransactionGroupListItemTest.tsx b/tests/unit/TransactionGroupListItemTest.tsx index a6d46d6544bc..085eefb48f38 100644 --- a/tests/unit/TransactionGroupListItemTest.tsx +++ b/tests/unit/TransactionGroupListItemTest.tsx @@ -21,6 +21,7 @@ jest.mock('@libs/actions/Search', () => ({ jest.mock('@libs/SearchUIUtils', () => ({ getSections: jest.fn(() => []), isCorrectSearchUserName: jest.fn(() => true), + getTableMinWidth: jest.fn(() => 0), })); const mockTransaction: TransactionListItemType = { diff --git a/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts b/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts index ce1c789a5127..acb23651009e 100644 --- a/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts +++ b/tests/unit/hooks/useSuggestedSearchDefaultNavigationTest.ts @@ -1,6 +1,5 @@ import {renderHook} from '@testing-library/react-native'; import useSuggestedSearchDefaultNavigation from '@hooks/useSuggestedSearchDefaultNavigation'; -import {clearAllFilters} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import {buildQueryStringFromFilterFormValues} from '@libs/SearchQueryUtils'; import type {SearchTypeMenuItem} from '@libs/SearchUIUtils'; @@ -9,10 +8,6 @@ import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; import type IconAsset from '@src/types/utils/IconAsset'; -jest.mock('@libs/actions/Search', () => ({ - clearAllFilters: jest.fn(), -})); - jest.mock('@libs/Navigation/Navigation', () => ({ navigate: jest.fn(), })); @@ -123,7 +118,6 @@ describe('useSuggestedSearchDefaultNavigation', () => { clearSelectedTransactions, }); - expect(clearAllFilters).toHaveBeenCalledTimes(1); expect(clearSelectedTransactions).toHaveBeenCalledTimes(1); expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.SEARCH_ROOT.getRoute({query: approveMenuItem.searchQuery})); }); @@ -170,7 +164,6 @@ describe('useSuggestedSearchDefaultNavigation', () => { }, }); - expect(clearAllFilters).not.toHaveBeenCalled(); expect(Navigation.navigate).not.toHaveBeenCalled(); }); @@ -190,7 +183,6 @@ describe('useSuggestedSearchDefaultNavigation', () => { }, }); - expect(clearAllFilters).not.toHaveBeenCalled(); expect(Navigation.navigate).not.toHaveBeenCalled(); }); @@ -217,7 +209,6 @@ describe('useSuggestedSearchDefaultNavigation', () => { shouldSkipNavigation: true, }); - expect(clearAllFilters).not.toHaveBeenCalled(); expect(clearSelectedTransactions).not.toHaveBeenCalled(); expect(Navigation.navigate).not.toHaveBeenCalled(); });