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/components/Search/index.tsx b/src/components/Search/index.tsx index f78581ee58b2..d11a9ee15123 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, isArchivedReport} from '@libs/ReportUtils'; -import {buildCannedSearchQuery, buildSearchQueryString} from '@libs/SearchQueryUtils'; +import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils'; import { getColumnsToShow, getListItem, @@ -101,6 +101,8 @@ function mapTransactionItemToSelectedEntry( reportID: item.reportID, policyID: item.policyID, amount: item.modifiedAmount ?? item.amount, + convertedAmount: item.convertedAmount, + convertedCurrency: item.convertedCurrency, }, ]; } @@ -178,6 +180,8 @@ function prepareTransactionsList( reportID: item.reportID, policyID: item.policyID, amount: Math.abs(item.modifiedAmount || item.amount), + convertedAmount: item.convertedAmount, + convertedCurrency: item.convertedCurrency, }, }; } @@ -211,6 +215,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, { @@ -250,13 +255,16 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS const suggestedSearches = useMemo(() => getSuggestedSearches(accountID, defaultCardFeed?.id), [defaultCardFeed?.id, accountID]); const {type, status, sortBy, sortOrder, hash, similarSearchHash, groupBy} = queryJSON; - const searchKey = useMemo(() => Object.values(suggestedSearches).find((search) => search.similarSearchHash === similarSearchHash)?.key, [suggestedSearches, similarSearchHash]); + const searchKey = useMemo(() => Object.values(suggestedSearches).find((search) => search.similarSearchHash === similarSearchHash)?.key, [suggestedSearches, similarSearchHash]); const shouldCalculateTotals = useMemo(() => { if (offset !== 0) { return false; } - if (!searchKey) { + + const savedSearchValues = Object.values(savedSearches ?? {}); + + if (!savedSearchValues.length && !searchKey) { return false; } @@ -270,8 +278,20 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CARD, CONST.SEARCH.SEARCH_KEYS.RECONCILIATION, ]; - return eligibleSearchKeys.includes(searchKey); - }, [offset, searchKey]); + + if (eligibleSearchKeys.includes(searchKey)) { + return true; + } + + for (const savedSearch of savedSearchValues) { + const searchData = buildSearchQueryJSON(savedSearch.query); + if (searchData && searchData.similarSearchHash === similarSearchHash) { + return true; + } + } + + return false; + }, [offset, savedSearches, searchKey, similarSearchHash]); const previousReportActions = usePrevious(reportActions); const reportActionsArray = useMemo( @@ -432,6 +452,8 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS reportID: transaction.reportID, policyID: transaction.policyID, amount: transaction.modifiedAmount ?? transaction.amount, + convertedAmount: transaction.convertedAmount, + convertedCurrency: transaction.convertedCurrency, }; }); }); @@ -461,6 +483,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 c695280b25a6..49cb3a44a8fb 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/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 989a003fed2e..fec2a31fce4e 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())); diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 871641787e4f..d94fcd2c0b9f 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -995,6 +995,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 5bc538acdb9a..378198ce4629 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -670,6 +670,16 @@ function SearchPage({route}: SearchPageProps) { } }, []); + 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), 0) : metadata?.total; + + return {count, total, currency}; + }, [areAllMatchingItemsSelected, metadata?.count, metadata?.currency, metadata?.total, selectedTransactions, selectedTransactionsKeys.length]); + if (shouldUseNarrowLayout) { return ( <> @@ -680,6 +690,7 @@ function SearchPage({route}: SearchPageProps) { headerButtonsOptions={headerButtonsOptions} searchResults={searchResults} isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} + footerData={footerData} /> - {shouldShowFooter && } + {shouldShowFooter && ( + + )} >; 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(); @@ -142,10 +147,9 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMob ); } + const shouldShowFooter = !!footerData?.count; const isDataLoaded = isSearchDataLoaded(searchResults, queryJSON); const shouldShowLoadingState = !isOffline && (!isDataLoaded || !!currentSearchResults?.search?.isLoading); - const metadata = searchResults?.search; - const shouldShowFooter = !!metadata?.count; return ( )} - {shouldShowFooter && } + {shouldShowFooter && ( + + )} ); diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 6e848b757926..1d8523ad40cf 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -428,11 +428,11 @@ 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 */ - convertedAmount?: number; + /** 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 */ - convertedCurrency?: string; + convertedCurrency: string; }; /** Model of tasks search result */ diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 069fe26f46ff..94348f9a3d3a 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, @@ -763,6 +773,8 @@ const transactionsListItems = [ type: CONST.VIOLATION_TYPES.VIOLATION, }, ], + convertedAmount: -5000, + convertedCurrency: 'USD', }, { accountID: 18439984, @@ -829,6 +841,8 @@ const transactionsListItems = [ isActionLoading: false, hasViolation: undefined, violations: [], + convertedAmount: -5000, + convertedCurrency: 'USD', }, { accountID: 18439984, @@ -895,6 +909,8 @@ const transactionsListItems = [ isActionLoading: false, hasViolation: undefined, violations: [], + convertedAmount: -5000, + convertedCurrency: 'USD', }, ] as TransactionListItemType[]; @@ -998,6 +1014,8 @@ const transactionReportGroupListItems = [ filename: undefined, isActionLoading: false, violations: [], + convertedAmount: -5000, + convertedCurrency: 'USD', }, ], type: 'expense', @@ -1107,6 +1125,8 @@ const transactionReportGroupListItems = [ errors: undefined, filename: undefined, isActionLoading: false, + convertedAmount: -5000, + convertedCurrency: 'USD', }, ], type: 'expense', @@ -2275,6 +2295,8 @@ describe('SearchUIUtils', () => { transactionID: '1805965960759424086', transactionThreadReportID: '4139222832581831', transactionType: 'cash', + convertedAmount: -5000, + convertedCurrency: 'USD', }, }, search: { 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,