From 32dee9c5f77de1305d497273d0baf0a459c2ff68 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Thu, 28 Aug 2025 09:49:18 -0400 Subject: [PATCH 01/15] always return total --- src/components/Search/index.tsx | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index dcd6ba7b2304..ee014970e592 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -47,7 +47,7 @@ import { shouldShowEmptyState, shouldShowYear as shouldShowYearUtil, } from '@libs/SearchUIUtils'; -import type {ArchivedReportsIDSet, SearchKey} from '@libs/SearchUIUtils'; +import type {ArchivedReportsIDSet} from '@libs/SearchUIUtils'; import {isOnHold, isTransactionPendingDelete} from '@libs/TransactionUtils'; import Navigation, {navigationRef} from '@navigation/Navigation'; import type {SearchFullscreenNavigatorParamList} from '@navigation/types'; @@ -235,26 +235,10 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS const [accountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false, selector: (s) => s?.accountID}); const suggestedSearches = useMemo(() => getSuggestedSearches(defaultCardFeed?.id, accountID), [defaultCardFeed?.id, accountID]); + const shouldCalculateTotals = offset === 0; const {type, status, sortBy, sortOrder, hash, similarSearchHash, groupBy} = queryJSON; const searchKey = useMemo(() => Object.values(suggestedSearches).find((search) => search.similarSearchHash === similarSearchHash)?.key, [suggestedSearches, similarSearchHash]); - const shouldCalculateTotals = useMemo(() => { - if (offset !== 0) { - return false; - } - if (!searchKey) { - return false; - } - - const eligibleSearchKeys: Partial = [ - CONST.SEARCH.SEARCH_KEYS.STATEMENTS, - CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CASH, - CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CARD, - CONST.SEARCH.SEARCH_KEYS.RECONCILIATION, - ]; - return eligibleSearchKeys.includes(searchKey); - }, [offset, searchKey]); - const previousReportActions = usePrevious(reportActions); const reportActionsArray = useMemo( () => From c3d821571fa1e7e1f6f86e71c934369b9074e5a2 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Thu, 28 Aug 2025 10:03:32 -0400 Subject: [PATCH 02/15] update prop types --- src/components/Search/SearchPageFooter.tsx | 11 ++++++----- src/pages/Search/SearchPage.tsx | 8 +++++++- src/pages/Search/SearchPageNarrow.tsx | 8 +++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/components/Search/SearchPageFooter.tsx b/src/components/Search/SearchPageFooter.tsx index bb7d7b65616b..7f428e162a7f 100644 --- a/src/components/Search/SearchPageFooter.tsx +++ b/src/components/Search/SearchPageFooter.tsx @@ -7,13 +7,14 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {convertToDisplayString} from '@libs/CurrencyUtils'; -import type {SearchResultsInfo} from '@src/types/onyx/SearchResults'; type SearchPageFooterProps = { - metadata: SearchResultsInfo; + count: number | undefined; + total: number | undefined; + currency: string | undefined; }; -function SearchPageFooter({metadata}: SearchPageFooterProps) { +function SearchPageFooter({count, total, currency}: SearchPageFooterProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -26,11 +27,11 @@ function SearchPageFooter({metadata}: SearchPageFooterProps) { {`${translate('common.expenses')}:`} - {metadata.count} + {count} {`${translate('common.totalSpend')}:`} - {convertToDisplayString(metadata.total, metadata.currency)} + {convertToDisplayString(total, currency)} ); diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index fa7be311fe30..469d384e0382 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -777,7 +777,13 @@ function SearchPage({route}: SearchPageProps) { handleSearch={handleSearchAction} isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} /> - {shouldShowFooter && } + {shouldShowFooter && ( + + )} )} - {shouldShowFooter && } + {shouldShowFooter && ( + + )} ); From 3cb192031c88c1efef0688faf2ee2ad6c8a88bd5 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Thu, 28 Aug 2025 10:19:25 -0400 Subject: [PATCH 03/15] add selection viewing --- src/components/Search/index.tsx | 8 ++++++++ src/components/Search/types.ts | 6 ++++++ src/libs/SearchUIUtils.ts | 2 ++ src/pages/Search/SearchPage.tsx | 23 ++++++++++++++++++++--- src/types/onyx/SearchResults.ts | 4 ++-- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index ee014970e592..ae2d9df435d8 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -99,6 +99,8 @@ function mapTransactionItemToSelectedEntry( reportID: item.reportID, policyID: item.policyID, amount: item.modifiedAmount ?? item.amount, + convertedAmount: item.convertedAmount, + convertedCurrency: item.convertedCurrency, }, ]; } @@ -165,6 +167,8 @@ function prepareTransactionsList( reportID: item.reportID, policyID: item.policyID, amount: Math.abs(item.modifiedAmount || item.amount), + convertedAmount: item.convertedAmount, + convertedCurrency: item.convertedCurrency, }, }; } @@ -397,6 +401,8 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS reportID: transaction.reportID, policyID: transaction.policyID, amount: transaction.modifiedAmount ?? transaction.amount, + convertedAmount: transaction.convertedAmount, + convertedCurrency: transaction.convertedCurrency, }; }); }); @@ -426,6 +432,8 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS reportID: transaction.reportID, policyID: transaction.policyID, amount: transaction.modifiedAmount ?? transaction.amount, + convertedAmount: transaction.convertedAmount, + convertedCurrency: transaction.convertedCurrency, }; }); } diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 370897cc9bc4..9a5a6e331589 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -35,6 +35,12 @@ type SelectedTransactionInfo = { /** The transaction amount */ amount: number; + + /** The converted transaction amount into either group currency, or the active policy currency */ + convertedAmount: number; + + /** The currency that the converted amount is in */ + convertedCurrency: string; }; /** Model of selected transactions */ diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 3e150730bc26..4e013a6f88bb 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -969,6 +969,8 @@ function getTransactionsSections( hasViolation: transactionItem.hasViolation, cardID: transactionItem.cardID, cardName: transactionItem.cardName, + convertedAmount: transactionItem.convertedAmount, + convertedCurrency: transactionItem.convertedCurrency, }; transactionsSections.push(transactionSection); diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 469d384e0382..11e4b16d695d 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -666,6 +666,23 @@ function SearchPage({route}: SearchPageProps) { } }, []); + const footerResults = useMemo(() => { + const count = selectedTransactionsKeys.length || metadata?.count; + const total = + selectedTransactionsKeys.length > 0 + ? Object.values(selectedTransactions).reduce((acc, transaction) => { + acc += transaction.convertedAmount; + return acc; + }, 0) + : metadata?.total; + + return { + count, + total, + currency: metadata?.currency, + }; + }, [metadata?.count, metadata?.currency, metadata?.total, selectedTransactions, selectedTransactionsKeys.length]); + if (shouldUseNarrowLayout) { return ( <> @@ -779,9 +796,9 @@ function SearchPage({route}: SearchPageProps) { /> {shouldShowFooter && ( )} diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 6e848b757926..c906f163b97e 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -429,10 +429,10 @@ type SearchTransaction = { cardName?: string; /** The converted amount of the transaction, if a currency conversion is used */ - convertedAmount?: number; + convertedAmount: number; /** The currency that the converted amount is in */ - convertedCurrency?: string; + convertedCurrency: string; }; /** Model of tasks search result */ From 2d16d7a435dcab9a25c1fb4ef5eafa50f21f512d Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Thu, 28 Aug 2025 10:23:49 -0400 Subject: [PATCH 04/15] finish the feature --- src/pages/Search/SearchPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 11e4b16d695d..77396e471d1e 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -667,11 +667,11 @@ function SearchPage({route}: SearchPageProps) { }, []); const footerResults = useMemo(() => { - const count = selectedTransactionsKeys.length || metadata?.count; + const count = selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected ? selectedTransactionsKeys.length : metadata?.count; const total = - selectedTransactionsKeys.length > 0 + selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected ? Object.values(selectedTransactions).reduce((acc, transaction) => { - acc += transaction.convertedAmount; + acc += -transaction.convertedAmount; return acc; }, 0) : metadata?.total; @@ -681,7 +681,7 @@ function SearchPage({route}: SearchPageProps) { total, currency: metadata?.currency, }; - }, [metadata?.count, metadata?.currency, metadata?.total, selectedTransactions, selectedTransactionsKeys.length]); + }, [areAllMatchingItemsSelected, metadata?.count, metadata?.currency, metadata?.total, selectedTransactions, selectedTransactionsKeys.length]); if (shouldUseNarrowLayout) { return ( From fefb9f54c823f19886f0ae923ad1428a19081f06 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Thu, 28 Aug 2025 10:26:52 -0400 Subject: [PATCH 05/15] cleanup code --- src/pages/Search/SearchPage.tsx | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 77396e471d1e..c35a46119ec9 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -666,21 +666,14 @@ function SearchPage({route}: SearchPageProps) { } }, []); - const footerResults = useMemo(() => { - const count = selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected ? selectedTransactionsKeys.length : metadata?.count; - const total = - selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected - ? Object.values(selectedTransactions).reduce((acc, transaction) => { - acc += -transaction.convertedAmount; - return acc; - }, 0) - : metadata?.total; - - return { - count, - total, - currency: metadata?.currency, - }; + const footerData = useMemo(() => { + const shouldUseClientTotal = selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected; + + const currency = metadata?.currency; + const count = shouldUseClientTotal ? selectedTransactionsKeys.length : metadata?.count; + const total = shouldUseClientTotal ? Object.values(selectedTransactions).reduce((acc, transaction) => acc - transaction.convertedAmount, 0) : metadata?.total; + + return {count, total, currency}; }, [areAllMatchingItemsSelected, metadata?.count, metadata?.currency, metadata?.total, selectedTransactions, selectedTransactionsKeys.length]); if (shouldUseNarrowLayout) { @@ -796,9 +789,9 @@ function SearchPage({route}: SearchPageProps) { /> {shouldShowFooter && ( )} From fe7a43360ce0078875ff978d003ad472118a0732 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Thu, 28 Aug 2025 10:31:38 -0400 Subject: [PATCH 06/15] add to narrow --- src/pages/Search/SearchPageNarrow.tsx | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/pages/Search/SearchPageNarrow.tsx b/src/pages/Search/SearchPageNarrow.tsx index 2a0129358153..f8a8104d9fd4 100644 --- a/src/pages/Search/SearchPageNarrow.tsx +++ b/src/pages/Search/SearchPageNarrow.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import Animated, {clamp, runOnJS, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -53,7 +53,7 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMob const {windowHeight} = useWindowDimensions(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const {clearSelectedTransactions} = useSearchContext(); + const {clearSelectedTransactions, selectedTransactions, areAllMatchingItemsSelected} = useSearchContext(); const [searchRouterListVisible, setSearchRouterListVisible] = useState(false); const {isOffline} = useNetwork(); const currentSearchResultsKey = queryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; @@ -65,6 +65,8 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMob const scrollOffset = useSharedValue(0); const topBarOffset = useSharedValue(StyleUtils.searchHeaderDefaultOffset); + const metadata = searchResults?.search; + const handleBackButtonPress = useCallback(() => { if (!isMobileSelectionModeEnabled) { return false; @@ -119,6 +121,17 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMob } }, []); + const footerData = useMemo(() => { + const selectedTransactionsKeys = Object.keys(selectedTransactions); + const shouldUseClientTotal = selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected; + + const currency = metadata?.currency; + const count = shouldUseClientTotal ? selectedTransactionsKeys.length : metadata?.count; + const total = shouldUseClientTotal ? Object.values(selectedTransactions).reduce((acc, transaction) => acc - transaction.convertedAmount, 0) : metadata?.total; + + return {count, total, currency}; + }, [areAllMatchingItemsSelected, metadata?.count, metadata?.currency, metadata?.total, selectedTransactions]); + if (!queryJSON) { return ( )} From 5d1cabf3c53a5650e39164996eb28158008c0714 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Fri, 29 Aug 2025 10:11:31 -0400 Subject: [PATCH 07/15] pass footer to searchPage --- src/pages/Search/SearchPage.tsx | 1 + src/pages/Search/SearchPageNarrow.tsx | 24 ++++++++---------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index c35a46119ec9..0c916020b7ea 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -686,6 +686,7 @@ function SearchPage({route}: SearchPageProps) { headerButtonsOptions={headerButtonsOptions} searchResults={searchResults} isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} + footerData={footerData} /> >; searchResults?: SearchResults; isMobileSelectionModeEnabled: boolean; + footerData: { + count: number | undefined; + total: number | undefined; + currency: string | undefined; + }; }; -function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMobileSelectionModeEnabled}: SearchPageNarrowProps) { +function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMobileSelectionModeEnabled, footerData}: SearchPageNarrowProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {windowHeight} = useWindowDimensions(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const {clearSelectedTransactions, selectedTransactions, areAllMatchingItemsSelected} = useSearchContext(); + const {clearSelectedTransactions} = useSearchContext(); const [searchRouterListVisible, setSearchRouterListVisible] = useState(false); const {isOffline} = useNetwork(); const currentSearchResultsKey = queryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; @@ -65,8 +70,6 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMob const scrollOffset = useSharedValue(0); const topBarOffset = useSharedValue(StyleUtils.searchHeaderDefaultOffset); - const metadata = searchResults?.search; - const handleBackButtonPress = useCallback(() => { if (!isMobileSelectionModeEnabled) { return false; @@ -121,17 +124,6 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMob } }, []); - const footerData = useMemo(() => { - const selectedTransactionsKeys = Object.keys(selectedTransactions); - const shouldUseClientTotal = selectedTransactionsKeys.length > 0 && !areAllMatchingItemsSelected; - - const currency = metadata?.currency; - const count = shouldUseClientTotal ? selectedTransactionsKeys.length : metadata?.count; - const total = shouldUseClientTotal ? Object.values(selectedTransactions).reduce((acc, transaction) => acc - transaction.convertedAmount, 0) : metadata?.total; - - return {count, total, currency}; - }, [areAllMatchingItemsSelected, metadata?.count, metadata?.currency, metadata?.total, selectedTransactions]); - if (!queryJSON) { return ( Date: Fri, 29 Aug 2025 10:22:00 -0400 Subject: [PATCH 08/15] only show the search for saved searches and suggested searches --- src/components/Search/index.tsx | 42 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 498c7a71c7f5..dd32ff026aeb 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -26,7 +26,7 @@ import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNa import Performance from '@libs/Performance'; import {getIOUActionForTransactionID, isExportIntegrationAction, isIntegrationMessageAction} from '@libs/ReportActionsUtils'; import {canEditFieldOfMoneyRequest, generateReportID, isArchivedReport} from '@libs/ReportUtils'; -import {buildCannedSearchQuery, buildSearchQueryString} from '@libs/SearchQueryUtils'; +import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils'; import { getColumnsToShow, getListItem, @@ -46,7 +46,7 @@ import { shouldShowEmptyState, shouldShowYear as shouldShowYearUtil, } from '@libs/SearchUIUtils'; -import type {ArchivedReportsIDSet} from '@libs/SearchUIUtils'; +import type {ArchivedReportsIDSet, SearchKey} from '@libs/SearchUIUtils'; import {isOnHold, isTransactionPendingDelete} from '@libs/TransactionUtils'; import Navigation, {navigationRef} from '@navigation/Navigation'; import type {SearchFullscreenNavigatorParamList} from '@navigation/types'; @@ -203,6 +203,7 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true}); const previousTransactions = usePrevious(transactions); const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true}); + const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES, {canBeMissing: true}); const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true}); const [archivedReportsIdSet = new Set()] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, { @@ -241,9 +242,44 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS const [accountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false, selector: (s) => s?.accountID}); const suggestedSearches = useMemo(() => getSuggestedSearches(defaultCardFeed?.id, accountID), [defaultCardFeed?.id, accountID]); - const shouldCalculateTotals = offset === 0; const {type, status, sortBy, sortOrder, hash, similarSearchHash, groupBy} = queryJSON; + const searchKey = useMemo(() => Object.values(suggestedSearches).find((search) => search.similarSearchHash === similarSearchHash)?.key, [suggestedSearches, similarSearchHash]); + const shouldCalculateTotals = useMemo(() => { + if (offset !== 0) { + return false; + } + + const savedSearchValues = Object.values(savedSearches ?? {}); + + if (!savedSearchValues.length && !searchKey) { + return false; + } + + const eligibleSearchKeys: Partial = [ + CONST.SEARCH.SEARCH_KEYS.SUBMIT, + CONST.SEARCH.SEARCH_KEYS.APPROVE, + CONST.SEARCH.SEARCH_KEYS.PAY, + CONST.SEARCH.SEARCH_KEYS.EXPORT, + CONST.SEARCH.SEARCH_KEYS.STATEMENTS, + CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CASH, + CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CARD, + CONST.SEARCH.SEARCH_KEYS.RECONCILIATION, + ]; + + if (eligibleSearchKeys.includes(searchKey)) { + return true; + } + + for (const savedSearch of savedSearchValues) { + const searchData = buildSearchQueryJSON(savedSearch.query); + if (searchData && searchData.hash === hash) { + return true; + } + } + + return false; + }, [hash, offset, savedSearches, searchKey]); const previousReportActions = usePrevious(reportActions); const reportActionsArray = useMemo( From cccbc955abd2208c750995267f23d802c2695935 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Fri, 29 Aug 2025 10:31:09 -0400 Subject: [PATCH 09/15] fix typ error --- tests/unit/Search/SearchUIUtilsTest.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 3d38fbc69eda..89dab71955e9 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -321,6 +321,8 @@ const searchResults: OnyxTypes.SearchResults = { errors: undefined, filename: undefined, isActionLoading: false, + convertedAmount: -5000, + convertedCurrency: 'USD', }, [`transactions_${transactionID2}`]: { accountID: adminAccountID, @@ -365,6 +367,8 @@ const searchResults: OnyxTypes.SearchResults = { errors: undefined, filename: undefined, isActionLoading: false, + convertedAmount: -5000, + convertedCurrency: 'USD', }, ...allViolations, [`transactions_${transactionID3}`]: { @@ -410,6 +414,8 @@ const searchResults: OnyxTypes.SearchResults = { filename: undefined, isActionLoading: false, hasViolation: undefined, + convertedAmount: -5000, + convertedCurrency: 'USD', }, [`transactions_${transactionID4}`]: { accountID: adminAccountID, @@ -454,6 +460,8 @@ const searchResults: OnyxTypes.SearchResults = { filename: undefined, isActionLoading: false, hasViolation: undefined, + convertedAmount: -5000, + convertedCurrency: 'USD', }, }, search: { @@ -692,6 +700,8 @@ const transactionsListItems = [ isActionLoading: false, hasViolation: false, violations: [], + convertedAmount: -5000, + convertedCurrency: 'USD', }, { accountID: 18439984, @@ -2218,6 +2228,8 @@ describe('SearchUIUtils', () => { transactionID: '1805965960759424086', transactionThreadReportID: '4139222832581831', transactionType: 'cash', + convertedAmount: -5000, + convertedCurrency: 'USD', }, }, search: { From d0dcc42f2af33d770517e72c0ab334908ea0c4c3 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Fri, 29 Aug 2025 11:49:57 -0400 Subject: [PATCH 10/15] fix lint --- tests/unit/Search/handleActionButtonPressTest.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/Search/handleActionButtonPressTest.ts b/tests/unit/Search/handleActionButtonPressTest.ts index 8f3b0004bba1..78e2c92eb7ed 100644 --- a/tests/unit/Search/handleActionButtonPressTest.ts +++ b/tests/unit/Search/handleActionButtonPressTest.ts @@ -139,6 +139,8 @@ const mockReportItemWithHold = { isAmountColumnWide: false, isTaxAmountColumnWide: false, shouldAnimateInHighlight: false, + convertedAmount: 1200, + convertedCurrency: 'USD', }, { report: { @@ -195,6 +197,8 @@ const mockReportItemWithHold = { isAmountColumnWide: false, isTaxAmountColumnWide: false, shouldAnimateInHighlight: false, + convertedAmount: 1200, + convertedCurrency: 'USD', }, ], isSelected: false, From f324bf72bd84e2e1ece1b499714c68f26f1fa071 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Fri, 29 Aug 2025 12:46:52 -0400 Subject: [PATCH 11/15] fix t ests --- tests/unit/Search/SearchUIUtilsTest.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 89dab71955e9..4bc9b969dd0e 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -773,6 +773,8 @@ const transactionsListItems = [ type: CONST.VIOLATION_TYPES.VIOLATION, }, ], + convertedAmount: -5000, + convertedCurrency: 'USD', }, { accountID: 18439984, @@ -839,6 +841,8 @@ const transactionsListItems = [ isActionLoading: false, hasViolation: undefined, violations: [], + convertedAmount: -5000, + convertedCurrency: 'USD', }, { accountID: 18439984, @@ -905,6 +909,8 @@ const transactionsListItems = [ isActionLoading: false, hasViolation: undefined, violations: [], + convertedAmount: -5000, + convertedCurrency: 'USD', }, ] as TransactionListItemType[]; @@ -1008,6 +1014,8 @@ const transactionReportGroupListItems = [ filename: undefined, isActionLoading: false, violations: [], + convertedAmount: -5000, + convertedCurrency: 'USD', }, ], type: 'expense', @@ -1117,6 +1125,8 @@ const transactionReportGroupListItems = [ errors: undefined, filename: undefined, isActionLoading: false, + convertedAmount: -5000, + convertedCurrency: 'USD', }, ], type: 'expense', From 6e2495531cedc292ce6cfbbd08741eb558b5c1f3 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Tue, 2 Sep 2025 11:48:32 -0400 Subject: [PATCH 12/15] add fallback for null --- src/pages/Search/SearchPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 0c916020b7ea..178a74634602 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -671,7 +671,7 @@ function SearchPage({route}: SearchPageProps) { const currency = metadata?.currency; const count = shouldUseClientTotal ? selectedTransactionsKeys.length : metadata?.count; - const total = shouldUseClientTotal ? Object.values(selectedTransactions).reduce((acc, transaction) => acc - transaction.convertedAmount, 0) : metadata?.total; + const total = shouldUseClientTotal ? Object.values(selectedTransactions).reduce((acc, transaction) => acc - (transaction.convertedAmount ?? 0), 0) : metadata?.total; return {count, total, currency}; }, [areAllMatchingItemsSelected, metadata?.count, metadata?.currency, metadata?.total, selectedTransactions, selectedTransactionsKeys.length]); From 94d0edcba9c88cc4c24c97d7ca9d4b9b6211221c Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Tue, 2 Sep 2025 11:57:26 -0400 Subject: [PATCH 13/15] update comment --- src/types/onyx/SearchResults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index c906f163b97e..1d8523ad40cf 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -428,7 +428,7 @@ type SearchTransaction = { /** The display name of the purchaser card, if any */ cardName?: string; - /** The converted amount of the transaction, if a currency conversion is used */ + /** The converted amount of the transaction, defaults to the active policies currency, or the converted currency if a currency conversion is used */ convertedAmount: number; /** The currency that the converted amount is in */ From de75188c5e35baa7c7bd7c4f2b9c7d82f2ed950b Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Wed, 3 Sep 2025 08:29:24 -0400 Subject: [PATCH 14/15] add similar search ignored keys --- src/libs/SearchQueryUtils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 6a05c485f7d8..22b3e12e84f1 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -302,9 +302,15 @@ function getQueryHashes(query: SearchQueryJSON): {primaryHash: number; recentSea const filterSet = new Set(orderedQuery); + // Certain filters shouldn't affect whether two searchers are similar or not, since they dont + // actually filter out results + const similarSearchIgnoredFilters = new Set([CONST.SEARCH.SYNTAX_FILTER_KEYS.GROUP_CURRENCY]); + query.flatFilters .map((filter) => { - filterSet.add(filter.key); + if (!similarSearchIgnoredFilters.has(filter.key)) { + filterSet.add(filter.key); + } const filters = cloneDeep(filter.filters); filters.sort((a, b) => customCollator.compare(a.value.toString(), b.value.toString())); From 32a693e0891305528471fa401b0d802911162548 Mon Sep 17 00:00:00 2001 From: Jack Senyitko Date: Thu, 4 Sep 2025 07:50:32 -0400 Subject: [PATCH 15/15] add similar search hash to saved searches --- src/components/Search/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 1beaba8df191..d11a9ee15123 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -285,13 +285,13 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS for (const savedSearch of savedSearchValues) { const searchData = buildSearchQueryJSON(savedSearch.query); - if (searchData && searchData.hash === hash) { + if (searchData && searchData.similarSearchHash === similarSearchHash) { return true; } } return false; - }, [hash, offset, savedSearches, searchKey]); + }, [offset, savedSearches, searchKey, similarSearchHash]); const previousReportActions = usePrevious(reportActions); const reportActionsArray = useMemo(