diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 649ca3a93173..d4bfe721c629 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1275,6 +1275,7 @@ const CONST = { RECEIPT: 'receipt', DATE: 'date', MERCHANT: 'merchant', + DESCRIPTION: 'description', FROM: 'from', TO: 'to', CATEGORY: 'category', @@ -6451,6 +6452,9 @@ const CONST = { UNAPPROVED_CASH: 'unapprovedCash', UNAPPROVED_CARD: 'unapprovedCard', }, + ANIMATION: { + FADE_DURATION: 200, + }, }, EXPENSE: { diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx index e588dde8b829..c83f12cb4ac5 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx @@ -13,25 +13,6 @@ type ColumnConfig = { isColumnSortable?: boolean; }; -const shouldShowColumnConfig: Record boolean> = { - [CONST.SEARCH.TABLE_COLUMNS.RECEIPT]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.TYPE]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.DATE]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.CATEGORY]: (isIOUReport) => !isIOUReport, - [CONST.SEARCH.TABLE_COLUMNS.TAG]: (isIOUReport) => !isIOUReport, - [CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.IN]: () => false, - [CONST.SEARCH.TABLE_COLUMNS.FROM]: () => false, - [CONST.SEARCH.TABLE_COLUMNS.TO]: () => false, - [CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]: () => false, - [CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT]: () => false, - [CONST.SEARCH.TABLE_COLUMNS.ACTION]: () => false, - [CONST.SEARCH.TABLE_COLUMNS.TITLE]: () => false, - [CONST.SEARCH.TABLE_COLUMNS.ASSIGNEE]: () => false, -}; - const columnConfig: ColumnConfig[] = [ { columnName: CONST.SEARCH.TABLE_COLUMNS.RECEIPT, @@ -51,6 +32,10 @@ const columnConfig: ColumnConfig[] = [ columnName: CONST.SEARCH.TABLE_COLUMNS.MERCHANT, translationKey: 'common.merchant', }, + { + columnName: CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION, + translationKey: 'common.description', + }, { columnName: CONST.SEARCH.TABLE_COLUMNS.CATEGORY, translationKey: 'common.category', @@ -78,22 +63,19 @@ type SearchTableHeaderProps = { amountColumnSize: TableColumnSize; taxAmountColumnSize: TableColumnSize; shouldShowSorting: boolean; - isIOUReport: boolean; + columns: SortableColumnName[]; }; -function MoneyRequestReportTableHeader({sortBy, sortOrder, onSortPress, dateColumnSize, shouldShowSorting, isIOUReport, amountColumnSize, taxAmountColumnSize}: SearchTableHeaderProps) { +function MoneyRequestReportTableHeader({sortBy, sortOrder, onSortPress, dateColumnSize, shouldShowSorting, amountColumnSize, taxAmountColumnSize, columns}: SearchTableHeaderProps) { const styles = useThemeStyles(); const shouldShowColumn = useCallback( (columnName: SortableColumnName) => { - const shouldShowFun = shouldShowColumnConfig[columnName]; - if (!shouldShowFun) { - return false; - } - return shouldShowFun(isIOUReport); + return columns.includes(columnName); }, - [isIOUReport], + [columns], ); + return ( void; }; function MoneyRequestReportTransactionItem({ transaction, + columns, isSelectionModeEnabled, toggleTransaction, isSelected, @@ -132,7 +127,7 @@ function MoneyRequestReportTransactionItem({ shouldUseNarrowLayout={shouldUseNarrowLayout || isMediumScreenWidth} shouldShowCheckbox={!!isSelectionModeEnabled || !isSmallScreenWidth} onCheckboxPress={toggleTransaction} - columns={allReportColumns} + columns={columns as Array>} isDisabled={isPendingDelete} /> diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index b75a76ba116b..575f725c3227 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -21,8 +21,8 @@ import {setActiveTransactionIDs} from '@libs/actions/TransactionThreadNavigation import {convertToDisplayString} from '@libs/CurrencyUtils'; import {navigationRef} from '@libs/Navigation/Navigation'; import {getIOUActionForTransactionID, getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; -import {generateReportID, getMoneyRequestSpendBreakdown, isIOUReport} from '@libs/ReportUtils'; -import {compareValues, isTransactionAmountTooLong, isTransactionTaxAmountTooLong} from '@libs/SearchUIUtils'; +import {generateReportID, getMoneyRequestSpendBreakdown} from '@libs/ReportUtils'; +import {compareValues, getColumnsToShow, isTransactionAmountTooLong, isTransactionTaxAmountTooLong} from '@libs/SearchUIUtils'; import {getTransactionPendingAction, isTransactionPendingDelete} from '@libs/TransactionUtils'; import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear'; import Navigation from '@navigation/Navigation'; @@ -164,6 +164,11 @@ function MoneyRequestReportTransactionList({ })); }, [newTransactions, sortBy, sortOrder, transactions, localeCompare]); + const columnsToShow = useMemo(() => { + const columns = getColumnsToShow(transactions, true); + return (Object.keys(columns) as SortableColumnName[]).filter((column) => columns[column]); + }, [transactions]); + const navigateToTransaction = useCallback( (activeTransactionID: string) => { const iouAction = getIOUActionForTransactionID(reportActions, activeTransactionID); @@ -274,6 +279,7 @@ function MoneyRequestReportTransactionList({ shouldShowSorting sortBy={sortBy} sortOrder={sortOrder} + columns={columnsToShow} dateColumnSize={dateColumnSize} amountColumnSize={amountColumnSize} taxAmountColumnSize={taxAmountColumnSize} @@ -284,7 +290,6 @@ function MoneyRequestReportTransactionList({ setSortConfig((prevState) => ({...prevState, sortBy: selectedSortBy, sortOrder: selectedSortOrder})); }} - isIOUReport={isIOUReport(report)} /> )} @@ -295,6 +300,7 @@ function MoneyRequestReportTransactionList({ , 'onScroll' | 'conten /** The search query */ queryJSON: SearchQueryJSON; + /** Columns to show */ + columns: SortableColumnName[]; + /** Called when the viewability of rows changes, as defined by the viewabilityConfig prop. */ onViewableItemsChanged?: (info: {changed: ViewToken[]; viewableItems: ViewToken[]}) => void; @@ -123,6 +133,7 @@ function SearchList( shouldPreventDefaultFocusOnSelectRow, shouldPreventLongPressRow, queryJSON, + columns, onViewableItemsChanged, onLayout, estimatedItemSize = ITEM_HEIGHTS.NARROW_WITHOUT_DRAWER.STANDARD, @@ -332,6 +343,7 @@ function SearchList( }} shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} queryJSONHash={hash} + columns={columns} policies={policies} isDisabled={isDisabled} allReports={allReports} @@ -348,6 +360,7 @@ function SearchList( onCheckboxPress, onSelectRow, policies, + columns, hash, groupBy, setFocusedIndex, @@ -445,7 +458,7 @@ function SearchList( onScroll={onScroll} showsVerticalScrollIndicator={false} ref={listRef} - extraData={[focusedIndex, isFocused]} + extraData={[focusedIndex, isFocused, columns]} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} ListFooterComponent={ListFooterComponent} diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 88939e695175..6051439dc563 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -1,8 +1,9 @@ import {findFocusedRoute, useFocusEffect, useIsFocused, useNavigation} from '@react-navigation/native'; import type {ContentStyle} from '@shopify/flash-list'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import type {NativeScrollEvent, NativeSyntheticEvent, ViewToken} from 'react-native'; +import type {NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {View} from 'react-native'; +import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import FullPageErrorView from '@components/BlockingViews/FullPageErrorView'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import SearchTableHeader from '@components/SelectionList/SearchTableHeader'; @@ -28,6 +29,7 @@ import {getIOUActionForTransactionID, getOriginalMessage, getReportAction, isExp import {canEditFieldOfMoneyRequest, generateReportID} from '@libs/ReportUtils'; import {buildCannedSearchQuery, buildSearchQueryString} from '@libs/SearchQueryUtils'; import { + getColumnsToShow, getListItem, getSections, getSortedSections, @@ -55,6 +57,7 @@ import SCREENS from '@src/SCREENS'; import type {ReportAction} from '@src/types/onyx'; import type SearchResults from '@src/types/onyx/SearchResults'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import arraysEqual from '@src/utils/arraysEqual'; import {useSearchContext} from './SearchContext'; import SearchList from './SearchList'; import SearchScopeProvider from './SearchScopeProvider'; @@ -522,26 +525,39 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS [hash, isMobileSelectionModeEnabled, toggleTransaction], ); - const onViewableItemsChanged = useCallback( - ({viewableItems}: {viewableItems: ViewToken[]}) => { - if (!isFocused) { - return; - } + const currentColumns = useMemo(() => { + if (!searchResults?.data) { + return []; + } + const columns = getColumnsToShow(searchResults?.data); - const isFirstItemVisible = viewableItems.at(0)?.index === 1; - // If the user is still loading the search results, or if they are scrolling down, don't refresh the search results - if (shouldShowLoadingState || !isFirstItemVisible) { - return; - } + return (Object.keys(columns) as SearchColumnType[]).filter((col) => columns[col]); + }, [searchResults?.data]); - // This line makes sure the app refreshes the search results when the user scrolls to the top. - // The backend sends items in parts based on the offset, with a limit on the number of items sent (pagination). - // As a result, it skips some items, for example, if the offset is 100, it sends the next items without the first ones. - // Therefore, when the user scrolls to the top, we need to refresh the search results. - setOffset(0); - }, - [shouldShowLoadingState, isFocused], - ); + // Custom animation for fade effect + const opacity = useSharedValue(1); + const animatedStyle = useAnimatedStyle(() => ({ + opacity: opacity.get(), + })); + + const previousColumns = usePrevious(currentColumns); + const [columnsToShow, setColumnsToShow] = useState([]); + + // If columns have changed, trigger an animation before settings columnsToShow to prevent + // new columns appearing before the fade out animation happens + useEffect(() => { + if ((previousColumns && currentColumns && arraysEqual(previousColumns, currentColumns)) || offset === 0 || isSmallScreenWidth) { + setColumnsToShow(currentColumns); + return; + } + + opacity.set( + withTiming(0, {duration: CONST.SEARCH.ANIMATION.FADE_DURATION}, () => { + setColumnsToShow(currentColumns); + opacity.set(withTiming(1, {duration: CONST.SEARCH.ANIMATION.FADE_DURATION})); + }), + ); + }, [previousColumns, currentColumns, setColumnsToShow, opacity, offset, isSmallScreenWidth]); const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; const isTask = type === CONST.SEARCH.DATA_TYPES.TASK; @@ -625,14 +641,24 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS ); }, [clearSelectedTransactions, data, groupBy, reportActionsArray, selectedTransactions, setSelectedTransactions]); - const onLayout = useCallback(() => handleSelectionListScroll(sortedSelectedData, searchListRef.current), [handleSelectionListScroll, sortedSelectedData]); + const onLayoutWithScrollRestore = useCallback(() => { + handleSelectionListScroll(sortedSelectedData, searchListRef.current); + // Restore scroll position after layout if needed + // Removed scroll position restoration logic + }, [handleSelectionListScroll, sortedSelectedData]); if (shouldShowLoadingState) { return ( - + + + ); } @@ -680,50 +706,56 @@ function Search({queryJSON, searchResults, onSearchListScroll, contentContainerS return ( - - ) - } - contentContainerStyle={{...contentContainerStyle, ...styles.pb3}} - containerStyle={[styles.pv0, type === CONST.SEARCH.DATA_TYPES.CHAT && !isSmallScreenWidth && styles.pt3]} - shouldPreventDefaultFocusOnSelectRow={!canUseTouchScreen()} - onScroll={onSearchListScroll} - onEndReachedThreshold={0.75} - onEndReached={fetchMoreResults} - ListFooterComponent={ - shouldShowLoadingMoreItems ? ( - - ) : undefined - } - queryJSON={queryJSON} - onViewableItemsChanged={onViewableItemsChanged} - onLayout={onLayout} - isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} - /> + + + ) + } + contentContainerStyle={{...contentContainerStyle, ...styles.pb3}} + containerStyle={[styles.pv0, type === CONST.SEARCH.DATA_TYPES.CHAT && !isSmallScreenWidth && styles.pt3]} + shouldPreventDefaultFocusOnSelectRow={!canUseTouchScreen()} + onScroll={onSearchListScroll} + onEndReachedThreshold={0.75} + onEndReached={fetchMoreResults} + ListFooterComponent={ + shouldShowLoadingMoreItems ? ( + + ) : undefined + } + queryJSON={queryJSON} + columns={columnsToShow} + onLayout={onLayoutWithScrollRestore} + isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} + /> + ); } diff --git a/src/components/SelectionList/Search/TransactionGroupListItem.tsx b/src/components/SelectionList/Search/TransactionGroupListItem.tsx index 646ef473ce1c..d0e452bf33c9 100644 --- a/src/components/SelectionList/Search/TransactionGroupListItem.tsx +++ b/src/components/SelectionList/Search/TransactionGroupListItem.tsx @@ -49,6 +49,7 @@ function TransactionGroupListItem({ onFocus, onLongPressRow, shouldSyncFocus, + columns, groupBy, policies, }: TransactionGroupListItemProps) { @@ -106,23 +107,6 @@ function TransactionGroupListItem({ }); }; - const sampleTransaction = groupItem.transactions.at(0); - const {COLUMNS} = CONST.REPORT.TRANSACTION_LIST; - - const columns = [ - COLUMNS.RECEIPT, - COLUMNS.TYPE, - COLUMNS.DATE, - COLUMNS.MERCHANT, - COLUMNS.FROM, - COLUMNS.TO, - ...(sampleTransaction?.shouldShowCategory ? [COLUMNS.CATEGORY] : []), - ...(sampleTransaction?.shouldShowTag ? [COLUMNS.TAG] : []), - ...(sampleTransaction?.shouldShowTax ? [COLUMNS.TAX] : []), - COLUMNS.TOTAL_AMOUNT, - COLUMNS.ACTION, - ] satisfies Array>; - const getHeader = useMemo(() => { const headers: Record = { [CONST.SEARCH.GROUP_BY.REPORTS]: ( @@ -220,7 +204,7 @@ function TransactionGroupListItem({ shouldUseNarrowLayout={!isLargeScreenWidth} shouldShowCheckbox={!!canSelectMultiple} onCheckboxPress={() => onCheckboxPress?.(transaction as unknown as TItem)} - columns={columns} + columns={columns as Array>} onButtonPress={() => { openReportInRHP(transaction); }} diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index b4cd616902a5..fb2380a1f64a 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -30,6 +30,7 @@ function TransactionListItem({ onLongPressRow, shouldSyncFocus, isLoading, + columns, }: TransactionListItemProps) { const transactionItem = item as unknown as TransactionListItemType; const styles = useThemeStyles(); @@ -60,24 +61,6 @@ function TransactionListItem({ }; }, [transactionItem]); - const columns = useMemo( - () => - [ - CONST.REPORT.TRANSACTION_LIST.COLUMNS.RECEIPT, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.TYPE, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.DATE, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.MERCHANT, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.FROM, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.TO, - ...(transactionItem?.shouldShowCategory ? [CONST.REPORT.TRANSACTION_LIST.COLUMNS.CATEGORY] : []), - ...(transactionItem?.shouldShowTag ? [CONST.REPORT.TRANSACTION_LIST.COLUMNS.TAG] : []), - ...(transactionItem?.shouldShowTax ? [CONST.REPORT.TRANSACTION_LIST.COLUMNS.TAX] : []), - CONST.REPORT.TRANSACTION_LIST.COLUMNS.TOTAL_AMOUNT, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.ACTION, - ] satisfies Array>, - [transactionItem?.shouldShowCategory, transactionItem?.shouldShowTag, transactionItem?.shouldShowTax], - ); - const handleActionButtonPress = useCallback(() => { handleActionButtonPressUtil(currentSearchHash, transactionItem, () => onSelectRow(item), shouldUseNarrowLayout && !!canSelectMultiple, currentSearchKey); }, [canSelectMultiple, currentSearchHash, currentSearchKey, item, onSelectRow, shouldUseNarrowLayout, transactionItem]); @@ -133,7 +116,7 @@ function TransactionListItem({ onButtonPress={handleActionButtonPress} onCheckboxPress={handleCheckboxPress} shouldUseNarrowLayout={!isLargeScreenWidth} - columns={columns} + columns={columns as Array>} isActionLoading={isLoading ?? transactionItem.isActionLoading} isSelected={!!transactionItem.isSelected} dateColumnSize={dateColumnSize} diff --git a/src/components/SelectionList/SearchTableHeader.tsx b/src/components/SelectionList/SearchTableHeader.tsx index 21130da633ea..7e6509a8a1ef 100644 --- a/src/components/SelectionList/SearchTableHeader.tsx +++ b/src/components/SelectionList/SearchTableHeader.tsx @@ -2,41 +2,18 @@ import React, {useCallback} from 'react'; import type {SearchColumnType, SortOrder} from '@components/Search/types'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getShouldShowMerchant} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type * as OnyxTypes from '@src/types/onyx'; import SortableTableHeader from './SortableTableHeader'; import type {SortableColumnName} from './types'; -type ShouldShowSearchColumnFn = (data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) => boolean; - type SearchColumnConfig = { columnName: SearchColumnType; translationKey: TranslationPaths; isColumnSortable?: boolean; }; -const shouldShowColumnConfig: Record = { - [CONST.SEARCH.TABLE_COLUMNS.RECEIPT]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.TYPE]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.DATE]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: (data: OnyxTypes.SearchResults['data']) => getShouldShowMerchant(data), - [CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]: (data: OnyxTypes.SearchResults['data']) => !getShouldShowMerchant(data), - [CONST.SEARCH.TABLE_COLUMNS.FROM]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.TO]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.CATEGORY]: (data, metadata) => metadata?.columnsToShow?.shouldShowCategoryColumn ?? false, - [CONST.SEARCH.TABLE_COLUMNS.TAG]: (data, metadata) => metadata?.columnsToShow?.shouldShowTagColumn ?? false, - [CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT]: (data, metadata) => metadata?.columnsToShow?.shouldShowTaxColumn ?? false, - [CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.ACTION]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.TITLE]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.ASSIGNEE]: () => true, - [CONST.SEARCH.TABLE_COLUMNS.IN]: () => true, - // This column is never displayed on Search - [CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS]: () => false, -}; - const expenseHeaders: SearchColumnConfig[] = [ { columnName: CONST.SEARCH.TABLE_COLUMNS.RECEIPT, @@ -134,7 +111,6 @@ const SearchColumns = { }; type SearchTableHeaderProps = { - data: OnyxTypes.SearchResults['data']; metadata: OnyxTypes.SearchResults['search']; sortBy?: SearchColumnType; sortOrder?: SortOrder; @@ -144,10 +120,10 @@ type SearchTableHeaderProps = { isTaxAmountColumnWide: boolean; shouldShowSorting: boolean; canSelectMultiple: boolean; + columns: SortableColumnName[]; }; function SearchTableHeader({ - data, metadata, sortBy, sortOrder, @@ -157,6 +133,7 @@ function SearchTableHeader({ canSelectMultiple, isAmountColumnWide, isTaxAmountColumnWide, + columns, }: SearchTableHeaderProps) { const styles = useThemeStyles(); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth @@ -165,10 +142,9 @@ function SearchTableHeader({ const shouldShowColumn = useCallback( (columnName: SortableColumnName) => { - const shouldShowFun = shouldShowColumnConfig[columnName]; - return shouldShowFun(data, metadata); + return columns.includes(columnName); }, - [data, metadata], + [columns], ); if (displayNarrowVersion) { diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 38108c795773..a2eca58c76c6 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -243,6 +243,9 @@ type TransactionListItemType = ListItem & /** Whether we should show the merchant column */ shouldShowMerchant: boolean; + /** Whether the description column should be shown */ + shouldShowDescription: boolean; + /** Whether we should show the category column */ shouldShowCategory: boolean; @@ -252,6 +255,12 @@ type TransactionListItemType = ListItem & /** Whether we should show the tax column */ shouldShowTax: boolean; + /** Whether we should show the From column */ + shouldShowFrom: boolean; + + /** Whether we should show the to column */ + shouldShowTo: boolean; + /** Whether we should show the transaction year. * This is true if at least one transaction in the dataset was created in past years */ @@ -454,6 +463,7 @@ type TableListItemProps = ListItemProps; type TransactionListItemProps = ListItemProps & { /** Whether the item's action is loading */ isLoading?: boolean; + columns?: SortableColumnName[]; }; type TaskListItemProps = ListItemProps & { @@ -464,6 +474,7 @@ type TaskListItemProps = ListItemProps & { type TransactionGroupListItemProps = ListItemProps & { groupBy?: SearchGroupBy; policies?: OnyxCollection; + columns?: SortableColumnName[]; }; type ChatListItemProps = ListItemProps & { diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 30a8c4a47bdb..b7e8ac8dad01 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -13,7 +13,6 @@ import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {isCategoryMissing} from '@libs/CategoryUtils'; -import Parser from '@libs/Parser'; import StringUtils from '@libs/StringUtils'; import { getDescription, @@ -68,6 +67,9 @@ type TransactionWithOptionalSearchFields = TransactionWithOptionalHighlight & { /** information about whether to show merchant, that is provided on Reports page */ shouldShowMerchant?: boolean; + /** information about whether to show the description, that is provided on Reports page */ + shouldShowDescription?: boolean; + /** Type of transaction */ transactionType?: ValueOf; @@ -94,24 +96,17 @@ type TransactionItemRowProps = { isDisabled?: boolean; }; -/** If merchant name is empty or (none), then it falls back to description if screen is narrow */ -function getMerchantNameWithFallback(transactionItem: TransactionWithOptionalSearchFields, translate: (key: TranslationPaths) => string, shouldUseNarrowLayout?: boolean | undefined) { +function getMerchantName(transactionItem: TransactionWithOptionalSearchFields, translate: (key: TranslationPaths) => string) { const shouldShowMerchant = transactionItem.shouldShowMerchant ?? true; - const description = getDescription(transactionItem); - let merchantOrDescriptionToDisplay = transactionItem?.formattedMerchant ?? getMerchant(transactionItem); - const merchantNameEmpty = !merchantOrDescriptionToDisplay || merchantOrDescriptionToDisplay === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - if (merchantNameEmpty && shouldUseNarrowLayout) { - merchantOrDescriptionToDisplay = Parser.htmlToText(description); - } - let merchant = shouldShowMerchant ? merchantOrDescriptionToDisplay : Parser.htmlToText(description); + let merchant = transactionItem?.formattedMerchant ?? getMerchant(transactionItem); if (isScanning(transactionItem) && shouldShowMerchant) { merchant = translate('iou.receiptStatusTitle'); } const merchantName = StringUtils.getFirstLine(merchant); - return merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ? merchantName : ''; + return merchantName !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ? merchantName : ''; } function TransactionItemRow({ @@ -150,7 +145,12 @@ function TransactionItemRow({ return styles.activeComponentBG; }, [isSelected, styles.activeComponentBG]); - const merchantOrDescriptionName = useMemo(() => getMerchantNameWithFallback(transactionItem, translate, shouldUseNarrowLayout), [shouldUseNarrowLayout, transactionItem, translate]); + const merchant = useMemo(() => getMerchantName(transactionItem, translate), [transactionItem, translate]); + const description = getDescription(transactionItem); + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const merchantOrDescription = merchant || description; + const missingFieldError = useMemo(() => { const isCustomUnitOutOfPolicy = isUnreportedAndHasInvalidDistanceRateTransaction(transactionItem); const hasFieldErrors = hasMissingSmartscanFields(transactionItem) || isCustomUnitOutOfPolicy; @@ -256,9 +256,23 @@ function TransactionItemRow({ key={CONST.REPORT.TRANSACTION_LIST.COLUMNS.MERCHANT} style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.MERCHANT)]} > - {!!merchantOrDescriptionName && ( + {!!merchant && ( + + )} + + ), + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.DESCRIPTION]: ( + + {!!description && ( @@ -338,7 +352,8 @@ function TransactionItemRow({ isTaxAmountColumnWide, isInSingleTransactionReport, isSelected, - merchantOrDescriptionName, + merchant, + description, onButtonPress, shouldShowTooltip, shouldUseNarrowLayout, @@ -384,7 +399,7 @@ function TransactionItemRow({ shouldShowTooltip={shouldShowTooltip} shouldUseNarrowLayout={shouldUseNarrowLayout} /> - {!merchantOrDescriptionName && ( + {!merchantOrDescription && ( )} - {!!merchantOrDescriptionName && ( + {!!merchantOrDescription && ( diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 10a01dbaab0d..b48e0857a813 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -17,6 +17,7 @@ import type { ListItem, ReportActionListItemType, SearchListItem, + SortableColumnName, TaskListItemType, TransactionCardGroupListItemType, TransactionGroupListItemType, @@ -76,17 +77,22 @@ import { isInvoiceReport, isMoneyRequestReport, isOpenExpenseReport, + isOpenReport, isSettled, } from './ReportUtils'; import {buildCannedSearchQuery, buildQueryStringFromFilterFormValues, buildSearchQueryJSON, getTodoSearchQuery} from './SearchQueryUtils'; import StringUtils from './StringUtils'; import {shouldRestrictUserBillableActions} from './SubscriptionUtils'; import { + getCategory, + getDescription, + getTag, getTaxAmount, getAmount as getTransactionAmount, getCreated as getTransactionCreatedDate, getMerchant as getTransactionMerchant, isPendingCardOrScanningTransaction, + isScanning, isUnreportedAndHasInvalidDistanceRateTransaction, isViolationDismissed, } from './TransactionUtils'; @@ -725,6 +731,9 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata const shouldShowCategory = metadata?.columnsToShow?.shouldShowCategoryColumn; const shouldShowTag = metadata?.columnsToShow?.shouldShowTagColumn; const shouldShowTax = metadata?.columnsToShow?.shouldShowTaxColumn; + const shouldShowTo = metadata?.columnsToShow?.shouldShowToColumn; + const shouldShowFrom = metadata?.columnsToShow?.shouldShowFromColumn; + const shouldShowDescription = metadata?.columnsToShow?.shouldShowDescriptionColumn; // Pre-filter transaction keys to avoid repeated checks const transactionKeys = Object.keys(data).filter(isTransactionEntry); @@ -762,6 +771,9 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata shouldShowCategory, shouldShowTag, shouldShowTax, + shouldShowTo, + shouldShowFrom, + shouldShowDescription, keyForList: transactionItem.transactionID, shouldShowYear: doesDataContainAPastYearTransaction, isAmountColumnWide: shouldShowAmountInWideColumn, @@ -1194,6 +1206,9 @@ function getReportSections( formattedMerchant, date, shouldShowMerchant, + shouldShowDescription: metadata?.columnsToShow.shouldShowDescriptionColumn, + shouldShowFrom: metadata?.columnsToShow.shouldShowFromColumn, + shouldShowTo: metadata?.columnsToShow.shouldShowToColumn, shouldShowCategory: metadata?.columnsToShow?.shouldShowCategoryColumn, shouldShowTag: metadata?.columnsToShow?.shouldShowTagColumn, shouldShowTax: metadata?.columnsToShow?.shouldShowTaxColumn, @@ -1319,6 +1334,94 @@ function getSortedSections( return getSortedTransactionData(data as TransactionListItemType[], localeCompare, sortBy, sortOrder); } +/** + * Determines what columns to show based on available data + * @param isExpenseReportView: true when we are inside an expense report view, false if we're in the Reports page. + */ +function getColumnsToShow(data: OnyxTypes.SearchResults['data'] | OnyxTypes.Transaction[], isExpenseReportView = false): Record { + const columns: Record = isExpenseReportView + ? { + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.RECEIPT]: true, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.TYPE]: true, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.DATE]: true, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.MERCHANT]: false, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.DESCRIPTION]: false, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.CATEGORY]: false, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.TAG]: false, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS]: true, + [CONST.REPORT.TRANSACTION_LIST.COLUMNS.TOTAL_AMOUNT]: true, + } + : { + [CONST.SEARCH.TABLE_COLUMNS.RECEIPT]: true, + [CONST.SEARCH.TABLE_COLUMNS.TYPE]: true, + [CONST.SEARCH.TABLE_COLUMNS.DATE]: true, + [CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: false, + [CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]: false, + [CONST.SEARCH.TABLE_COLUMNS.FROM]: false, + [CONST.SEARCH.TABLE_COLUMNS.TO]: false, + [CONST.SEARCH.TABLE_COLUMNS.CATEGORY]: false, + [CONST.SEARCH.TABLE_COLUMNS.TAG]: false, + [CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT]: false, + [CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: true, + [CONST.SEARCH.TABLE_COLUMNS.ACTION]: true, + [CONST.SEARCH.TABLE_COLUMNS.TITLE]: true, + [CONST.SEARCH.TABLE_COLUMNS.ASSIGNEE]: true, + [CONST.SEARCH.TABLE_COLUMNS.IN]: true, + }; + + const updateColumns = (transaction: OnyxTypes.Transaction | SearchTransaction) => { + const merchant = transaction.modifiedMerchant ? transaction.modifiedMerchant : (transaction.merchant ?? ''); + if ((merchant !== '' && merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT) || isScanning(transaction)) { + columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.MERCHANT] = true; + } + + if (getDescription(transaction) !== '') { + columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.DESCRIPTION] = true; + } + + const category = getCategory(transaction); + const categoryEmptyValues = CONST.SEARCH.CATEGORY_EMPTY_VALUE.split(','); + if (category !== '' && !categoryEmptyValues.includes(category)) { + columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.CATEGORY] = true; + } + + const tag = getTag(transaction); + if (tag !== '' && tag !== CONST.SEARCH.TAG_EMPTY_VALUE) { + columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.TAG] = true; + } + + if (isExpenseReportView) { + return; + } + + // Handle From&To columns that are only shown in the Reports page + // if From or To differ from current user in any transaction, show the columns + const accountID = (transaction as SearchTransaction).accountID; + if (accountID !== currentAccountID) { + columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.FROM] = true; + } + + const managerID = (transaction as SearchTransaction).managerID; + if (managerID && managerID !== currentAccountID && !columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.TO]) { + const report = (data as OnyxTypes.SearchResults['data'])[`${ONYXKEYS.COLLECTION.REPORT}${transaction.reportID}`]; + columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.TO] = !!report && !isOpenReport(report); + } + }; + + if (Array.isArray(data)) { + data.forEach(updateColumns); + } else { + Object.keys(data).forEach((key) => { + if (!isTransactionEntry(key)) { + return; + } + updateColumns(data[key]); + }); + } + + return columns; +} + /** * Compares two values based on a specified sorting order and column. * Handles both string and numeric comparisons, with special handling for absolute values when sorting by total amount. @@ -1766,6 +1869,7 @@ export { isReportActionEntry, isTaskListItemType, getAction, + getColumnsToShow, createTypeMenuSections, createBaseSavedSearchMenuItem, shouldShowEmptyState, diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index d370b62706e2..85ea2213270c 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -35,6 +35,12 @@ type ListItemDataType = C ext /** Model of columns to show for search results */ type ColumnsToShow = { + /** Whether the From column should be shown */ + shouldShowFromColumn: boolean; + + /** Whether the To column should be shown */ + shouldShowToColumn: boolean; + /** Whether the category column should be shown */ shouldShowCategoryColumn: boolean; @@ -43,6 +49,9 @@ type ColumnsToShow = { /** Whether the tax column should be shown */ shouldShowTaxColumn: boolean; + + /** Whether the description column should be shown */ + shouldShowDescriptionColumn: boolean; }; /** Model of search result state */ diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 3275c2117a62..959f26808fe4 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -399,6 +399,9 @@ const searchResults: OnyxTypes.SearchResults = { shouldShowCategoryColumn: true, shouldShowTagColumn: false, shouldShowTaxColumn: false, + shouldShowFromColumn: true, + shouldShowToColumn: true, + shouldShowDescriptionColumn: false, }, hasMoreResults: false, hasResults: true, @@ -480,10 +483,13 @@ const transactionsListItems = [ reportID: '123456789', reportType: 'expense', shouldShowCategory: true, + shouldShowDescription: false, shouldShowMerchant: true, shouldShowTag: false, shouldShowTax: false, shouldShowYear: true, + shouldShowFrom: true, + shouldShowTo: true, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -547,6 +553,9 @@ const transactionsListItems = [ shouldShowTag: false, shouldShowTax: false, shouldShowYear: true, + shouldShowFrom: true, + shouldShowTo: true, + shouldShowDescription: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -626,6 +635,9 @@ const transactionsListItems = [ shouldShowTax: false, keyForList: '3', shouldShowYear: true, + shouldShowFrom: true, + shouldShowTo: true, + shouldShowDescription: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, receipt: undefined, @@ -690,6 +702,9 @@ const transactionsListItems = [ shouldShowTax: false, keyForList: '3', shouldShowYear: true, + shouldShowFrom: true, + shouldShowTo: true, + shouldShowDescription: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, receipt: undefined, @@ -783,6 +798,9 @@ const transactionReportGroupListItems = [ shouldShowTag: false, shouldShowTax: false, shouldShowYear: true, + shouldShowFrom: true, + shouldShowTo: true, + shouldShowDescription: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -889,6 +907,9 @@ const transactionReportGroupListItems = [ shouldShowTag: false, shouldShowTax: false, shouldShowYear: true, + shouldShowFrom: true, + shouldShowTo: true, + shouldShowDescription: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -1514,6 +1535,9 @@ describe('SearchUIUtils', () => { shouldShowCategoryColumn: true, shouldShowTagColumn: true, shouldShowTaxColumn: true, + shouldShowFromColumn: true, + shouldShowToColumn: true, + shouldShowDescriptionColumn: false, }, }, }; @@ -1556,4 +1580,189 @@ describe('SearchUIUtils', () => { expect(isAmountLengthLong3).toBe(true); expect(isTaxAmountLengthLong2).toBe(true); }); + + describe('Test getColumnsToShow', () => { + test('Should only show columns when at least one transaction has a value for them', async () => { + await Onyx.merge(ONYXKEYS.SESSION, {accountID: submitterAccountID}); + + // Use the existing transaction as a base and modify only the fields we need to test + const baseTransaction = searchResults.data[`transactions_${transactionID}`]; + + // Create test transactions as arrays (getColumnsToShow accepts arrays) + const emptyTransaction = { + ...baseTransaction, + transactionID: 'empty', + merchant: '', + modifiedMerchant: '', + comment: {comment: ''}, + category: '', + tag: '', + accountID: submitterAccountID, + managerID: submitterAccountID, + }; + + const merchantTransaction = { + ...baseTransaction, + transactionID: 'merchant', + merchant: 'Test Merchant', + modifiedMerchant: '', + comment: {comment: ''}, + category: '', + tag: '', + accountID: submitterAccountID, + managerID: submitterAccountID, + }; + + const categoryTransaction = { + ...baseTransaction, + transactionID: 'category', + merchant: '', + modifiedMerchant: '', + comment: {comment: ''}, + category: 'Office Supplies', + tag: '', + accountID: submitterAccountID, + managerID: submitterAccountID, + }; + + const tagTransaction = { + ...baseTransaction, + transactionID: 'tag', + merchant: '', + modifiedMerchant: '', + comment: {comment: ''}, + category: '', + tag: 'Project A', + accountID: submitterAccountID, + managerID: submitterAccountID, + }; + + const descriptionTransaction = { + ...baseTransaction, + transactionID: 'description', + merchant: '', + modifiedMerchant: '', + comment: {comment: 'Business meeting lunch'}, + category: '', + tag: '', + accountID: submitterAccountID, + managerID: submitterAccountID, + }; + + const differentUsersTransaction = { + ...baseTransaction, + transactionID: 'differentUsers', + merchant: '', + modifiedMerchant: '', + comment: {comment: ''}, + category: '', + tag: '', + accountID: approverAccountID, // Different from current user + managerID: adminAccountID, // Different from current user + reportID: reportID2, // Needs to be a submitter report for 'To' to show + }; + + // Test 1: No optional fields should be shown when all transactions are empty + let columns = SearchUIUtils.getColumnsToShow([emptyTransaction, emptyTransaction], false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]).toBe(false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.CATEGORY]).toBe(false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.TAG]).toBe(false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]).toBe(false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.FROM]).toBe(false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.TO]).toBe(false); + + // Test 2: Merchant column should show when at least one transaction has merchant + columns = SearchUIUtils.getColumnsToShow([emptyTransaction, merchantTransaction], false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.CATEGORY]).toBe(false); + + // Test 3: Category column should show when at least one transaction has category + columns = SearchUIUtils.getColumnsToShow([emptyTransaction, categoryTransaction], false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.CATEGORY]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]).toBe(false); + + // Test 4: Tag column should show when at least one transaction has tag + columns = SearchUIUtils.getColumnsToShow([emptyTransaction, tagTransaction], false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.TAG]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.CATEGORY]).toBe(false); + + // Test 5: Description column should show when at least one transaction has description + columns = SearchUIUtils.getColumnsToShow([emptyTransaction, descriptionTransaction], false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]).toBe(false); + + // Test 6: From/To columns should show when at least one transaction has different users + // @ts-expect-error -- no need to construct all data again, the function below only needs the report and transactions + const data: OnyxTypes.SearchResults['data'] = { + [`report_${reportID2}`]: searchResults.data[`report_${reportID2}`], + [`transactions_${emptyTransaction.transactionID}`]: emptyTransaction, + [`transactions_${differentUsersTransaction.transactionID}`]: differentUsersTransaction, + }; + columns = SearchUIUtils.getColumnsToShow(data, false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.FROM]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.TO]).toBe(true); + + // Test 7: Multiple columns should show when transactions have different fields + columns = SearchUIUtils.getColumnsToShow([merchantTransaction, categoryTransaction, tagTransaction], false); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.CATEGORY]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.TAG]).toBe(true); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]).toBe(false); + }); + + test('Should respect isExpenseReportView flag and not show From/To columns', () => { + // Create transaction with different users using existing transaction as base + const baseTransaction = searchResults.data[`transactions_${transactionID}`]; + const testTransaction = { + ...baseTransaction, + transactionID: 'test', + merchant: 'Test Merchant', + modifiedMerchant: '', + comment: {comment: 'Test description'}, + category: 'Office Supplies', + tag: 'Project A', + accountID: submitterAccountID, // Different from current user + managerID: approverAccountID, // Different from current user + }; + + // In expense report view, From/To columns should not be shown + const columns = SearchUIUtils.getColumnsToShow([testTransaction], true); + + // These columns should be shown based on data + expect(columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.MERCHANT]).toBe(true); + expect(columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.CATEGORY]).toBe(true); + expect(columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.TAG]).toBe(true); + expect(columns[CONST.REPORT.TRANSACTION_LIST.COLUMNS.DESCRIPTION]).toBe(true); + + // From/To columns should not exist in expense report view + expect(columns[CONST.SEARCH.TABLE_COLUMNS.FROM]).toBeUndefined(); + expect(columns[CONST.SEARCH.TABLE_COLUMNS.TO]).toBeUndefined(); + }); + + test('Should handle modifiedMerchant and empty category/tag values correctly', () => { + const baseTransaction = searchResults.data[`transactions_${transactionID}`]; + const testTransaction = { + ...baseTransaction, + transactionID: 'modified', + merchant: '', + modifiedMerchant: 'Modified Merchant', + comment: {comment: ''}, + category: 'Uncategorized', // This is in CONST.SEARCH.CATEGORY_EMPTY_VALUE + tag: CONST.SEARCH.TAG_EMPTY_VALUE, // This is the empty tag value + accountID: adminAccountID, + managerID: adminAccountID, + }; + + const columns = SearchUIUtils.getColumnsToShow([testTransaction], false); + + // Should show merchant column because modifiedMerchant has value + expect(columns[CONST.SEARCH.TABLE_COLUMNS.MERCHANT]).toBe(true); + + // Should not show category column because 'Uncategorized' is an empty value + expect(columns[CONST.SEARCH.TABLE_COLUMNS.CATEGORY]).toBe(false); + + // Should not show tag column because it's the empty tag value + expect(columns[CONST.SEARCH.TABLE_COLUMNS.TAG]).toBe(false); + }); + }); }); diff --git a/tests/unit/Search/handleActionButtonPressTest.ts b/tests/unit/Search/handleActionButtonPressTest.ts index bdd52532dd5b..55c80bcad1c5 100644 --- a/tests/unit/Search/handleActionButtonPressTest.ts +++ b/tests/unit/Search/handleActionButtonPressTest.ts @@ -183,6 +183,9 @@ const mockReportItemWithHold = { shouldShowCategory: true, shouldShowTag: false, shouldShowTax: false, + shouldShowTo: true, + shouldShowFrom: true, + shouldShowDescription: false, keyForList: '5345995386715609966', shouldShowYear: false, isAmountColumnWide: false, diff --git a/tests/unit/useSearchHighlightAndScrollTest.ts b/tests/unit/useSearchHighlightAndScrollTest.ts index e42d23c65f3c..832d4e696c4e 100644 --- a/tests/unit/useSearchHighlightAndScrollTest.ts +++ b/tests/unit/useSearchHighlightAndScrollTest.ts @@ -36,7 +36,14 @@ describe('useSearchHighlightAndScroll', () => { personalDetailsList: {}, }, search: { - columnsToShow: {shouldShowCategoryColumn: true, shouldShowTagColumn: true, shouldShowTaxColumn: true}, + columnsToShow: { + shouldShowCategoryColumn: true, + shouldShowTagColumn: true, + shouldShowTaxColumn: true, + shouldShowToColumn: true, + shouldShowFromColumn: true, + shouldShowDescriptionColumn: true, + }, hasMoreResults: false, hasResults: true, offset: 0,