diff --git a/src/components/ColumnsSettingsList.tsx b/src/components/ColumnsSettingsList.tsx index 934a38f50af9..54505685b2f0 100644 --- a/src/components/ColumnsSettingsList.tsx +++ b/src/components/ColumnsSettingsList.tsx @@ -7,6 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {getSearchColumnTranslationKey} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; +import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import React, {useRef, useState} from 'react'; import {View} from 'react-native'; @@ -71,9 +72,22 @@ type ColumnsSettingsListProps = { /** Callback fired with the updated column list when the user saves changes */ onSave: (columns: SearchCustomColumnIds[]) => void; + + /** The active Search type. The date column reads "Created" only for report-style types (expense report, invoice), otherwise "Date". */ + type?: SearchDataTypes; }; -function ColumnsSettingsList({allColumns, defaultSelectedColumns, currentColumns, requiredColumns, groupBy, groupColumns = [], defaultGroupColumns = [], onSave}: ColumnsSettingsListProps) { +function ColumnsSettingsList({ + allColumns, + defaultSelectedColumns, + currentColumns, + requiredColumns, + groupBy, + groupColumns = [], + defaultGroupColumns = [], + onSave, + type, +}: ColumnsSettingsListProps) { const theme = useTheme(); const styles = useThemeStyles(); const icons = useMemoizedLazyExpensifyIcons(['DragHandles']); @@ -89,8 +103,8 @@ function ColumnsSettingsList({allColumns, defaultSelectedColumns, currentColumns const unselected = columnsToSort .filter((col) => !col.isSelected) .sort((a, b) => { - const textA = translate(getSearchColumnTranslationKey(a.value)); - const textB = translate(getSearchColumnTranslationKey(b.value)); + const textA = translate(getSearchColumnTranslationKey(a.value, type)); + const textB = translate(getSearchColumnTranslationKey(b.value, type)); return localeCompare(textA, textB); }); return [...selected, ...unselected]; @@ -122,7 +136,7 @@ function ColumnsSettingsList({allColumns, defaultSelectedColumns, currentColumns const isEffectivelySelected = isRequired || isSelected; const isDragDisabled = !isEffectivelySelected; return { - text: translate(getSearchColumnTranslationKey(columnId)), + text: translate(getSearchColumnTranslationKey(columnId, type)), value: columnId, keyForList: columnId, isSelected: isEffectivelySelected, @@ -168,8 +182,8 @@ function ColumnsSettingsList({allColumns, defaultSelectedColumns, currentColumns const selectedCols = prevColumns.filter((col) => col.isSelected); const unselected = prevColumns.filter((col) => !col.isSelected && col.columnId !== updatedColumnId); const unselectedSorted = unselected.sort((a, b) => { - const textA = translate(getSearchColumnTranslationKey(a.columnId)); - const textB = translate(getSearchColumnTranslationKey(b.columnId)); + const textA = translate(getSearchColumnTranslationKey(a.columnId, type)); + const textB = translate(getSearchColumnTranslationKey(b.columnId, type)); return localeCompare(textA, textB); }); return [...selectedCols, {columnId: updatedColumnId, isSelected: true}, ...unselectedSorted]; diff --git a/src/components/Search/ExpenseFlatSearchView.tsx b/src/components/Search/ExpenseFlatSearchView.tsx index 3e93f9c278b0..83f97be8b486 100644 --- a/src/components/Search/ExpenseFlatSearchView.tsx +++ b/src/components/Search/ExpenseFlatSearchView.tsx @@ -107,6 +107,7 @@ function ExpenseFlatSearchView({ canSelectMultiple={canSelectMultiple} item={item} columns={columns} + isDateColumnCreated={type === CONST.SEARCH.DATA_TYPES.INVOICE} isDisabled={isDisabled} lastPaymentMethod={lastPaymentMethod} personalPolicyID={personalPolicyID} diff --git a/src/components/Search/FilterComponents/AdvancedFilters/FilterList.tsx b/src/components/Search/FilterComponents/AdvancedFilters/FilterList.tsx index ad89d06543a5..d9d2f87d744f 100644 --- a/src/components/Search/FilterComponents/AdvancedFilters/FilterList.tsx +++ b/src/components/Search/FilterComponents/AdvancedFilters/FilterList.tsx @@ -13,7 +13,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import {FILTER_VIEW_MAP} from '@libs/SearchUIUtils'; +import {FILTER_VIEW_MAP, getFilterViewLabelKey} from '@libs/SearchUIUtils'; import type {SearchFilter} from '@libs/SearchUIUtils'; import variables from '@styles/variables'; @@ -43,15 +43,17 @@ type FilterListProps = FilterItemCallbacks & { type FilterItemProps = FilterItemCallbacks & { filterKey: SearchFilter['key']; isSelected?: boolean; + type: SearchDataTypes | undefined; }; -function FilterItem({filterKey, isSelected, onPress, onHoverIn, onFocus}: FilterItemProps) { +function FilterItem({filterKey, isSelected, type, onPress, onHoverIn, onFocus}: FilterItemProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const theme = useTheme(); - const {labelKey, icon} = FILTER_VIEW_MAP[filterKey]; + const {icon} = FILTER_VIEW_MAP[filterKey]; + const labelKey = getFilterViewLabelKey(filterKey, type); const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', icon]); const getPressableBackgroundStyle = (pressed: boolean) => { @@ -124,6 +126,7 @@ function FilterList({type, policyID, selectedFilter, style, contentContainerStyl setSelectedDisplayFilter(CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY)} sentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_SORT_BY} /> diff --git a/src/components/Search/FilterDropdowns/SortByPopup.tsx b/src/components/Search/FilterDropdowns/SortByPopup.tsx index ab5d17990869..76d4a5814353 100644 --- a/src/components/Search/FilterDropdowns/SortByPopup.tsx +++ b/src/components/Search/FilterDropdowns/SortByPopup.tsx @@ -56,7 +56,7 @@ function SortByPopup({searchResults, queryJSON, groupBy, onSort, onSortOrderPres const currentColumns = !searchResults?.data ? [] : getColumnsToShow({currentAccountID: accountID, data: searchResults.data, visibleColumns, type: searchDataType, groupBy: groupBy?.value, sortBy: queryJSON.sortBy}); - const sortableColumns = getSortByOptions(currentColumns, translate); + const sortableColumns = getSortByOptions(currentColumns, translate, searchDataType); const sortOrder = queryJSON.sortOrder; const [selectedItem, setSelectedItem] = useState(queryJSON.sortBy); diff --git a/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx b/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx index 92d99d60048a..6f7537b8ff55 100644 --- a/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx +++ b/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx @@ -71,7 +71,7 @@ function ExpenseReportListItemRowWide({ /> ), [CONST.SEARCH.TABLE_COLUMNS.DATE]: ( - + ({ onLongPressRow, shouldSyncFocus, columns, + isDateColumnCreated, isLoading, isActionLoading, isLastItem, @@ -194,6 +195,7 @@ function TransactionListItemWide({ isDisabled={!!isDisabled} shouldDisableActionPointerEvents={shouldDisableActionPointerEvents} dateColumnSize={dateColumnSize} + isDateColumnCreated={isDateColumnCreated} submittedColumnSize={submittedColumnSize} approvedColumnSize={approvedColumnSize} postedColumnSize={postedColumnSize} diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx index 29898abe96b9..04189d399f23 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx @@ -81,6 +81,7 @@ function TransactionListItemInner({ onLongPressRow, shouldSyncFocus, columns, + isDateColumnCreated, isLoading, nonPersonalAndWorkspaceCards, lastPaymentMethod, @@ -267,6 +268,7 @@ function TransactionListItemInner({ onLongPressRow, shouldSyncFocus, columns, + isDateColumnCreated, isLoading, isActionLoading, transactionViolations, diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts b/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts index 57e823273cb4..ba17e8f923eb 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts @@ -20,6 +20,8 @@ type TransactionListItemSharedProps = { onLongPressRow?: (item: TItem) => void; shouldSyncFocus?: boolean; columns?: SearchColumnType[]; + /** When true, the date column renders as the wider "Created" column (report-style Search: invoice/expense-report) */ + isDateColumnCreated?: boolean; isLoading?: boolean; isActionLoading?: boolean; isLastItem?: boolean; diff --git a/src/components/Search/SearchList/ListItem/types.ts b/src/components/Search/SearchList/ListItem/types.ts index aa6db677b362..bf835ef52608 100644 --- a/src/components/Search/SearchList/ListItem/types.ts +++ b/src/components/Search/SearchList/ListItem/types.ts @@ -471,6 +471,8 @@ type TransactionListItemProps = ListItemProps & /** Whether the item's action is loading */ isLoading?: boolean; columns?: SearchColumnType[]; + /** When true, the date column renders as the wider "Created" column (report-style Search: invoice/expense-report) */ + isDateColumnCreated?: boolean; /** Non-personal and workspace cards for company card display */ nonPersonalAndWorkspaceCards?: CardList; /** All policies' tag lists, drilled from the list level so each row can resolve its policy's tags without an Onyx subscription per row */ diff --git a/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx index ecfbbd72163c..d7cbbab6c345 100644 --- a/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx @@ -17,7 +17,7 @@ import {shouldShowInitialCategoryFilterLoading} from '@hooks/useSearchFilterSync import {close} from '@libs/actions/Modal'; import {setSearchContext} from '@libs/actions/Search'; import {getAdvancedFiltersToReset, removeNegation} from '@libs/SearchQueryUtils'; -import {FILTER_VIEW_MAP, isAmountFilterKey, isDateFilterKey, isReportFieldKey, isTextFilterKey, mapFiltersFormToLabelValueList, SKIPPED_SEARCH_FILTERS} from '@libs/SearchUIUtils'; +import {getFilterViewLabelKey, isAmountFilterKey, isDateFilterKey, isReportFieldKey, isTextFilterKey, mapFiltersFormToLabelValueList, SKIPPED_SEARCH_FILTERS} from '@libs/SearchUIUtils'; import type {SearchFilter} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; @@ -59,7 +59,7 @@ function getFilterSentryLabel(filterKey: SearchAdvancedFiltersKey | SearchFilter function FilterPopup({baseFilterKey, searchAdvancedFiltersForm, closeOverlay, setPopoverWidth, updateFilterForm}: FilterPopupProps) { const {translate} = useLocalize(); - const label = translate(FILTER_VIEW_MAP[baseFilterKey].labelKey); + const label = translate(getFilterViewLabelKey(baseFilterKey, searchAdvancedFiltersForm.type)); const closeModalAndUpdateFilterForm = (values: Partial) => { close(() => updateFilterForm(values)); diff --git a/src/components/Search/SearchStaticList.tsx b/src/components/Search/SearchStaticList.tsx index e497b5710614..015019e2fded 100644 --- a/src/components/Search/SearchStaticList.tsx +++ b/src/components/Search/SearchStaticList.tsx @@ -15,7 +15,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {hasDeferredWrite} from '@libs/deferredLayoutWrite'; import Navigation from '@libs/Navigation/Navigation'; import {getReportStatusColorStyle, getReportStatusTooltipTranslation, getReportStatusTranslation, isOneTransactionReport} from '@libs/ReportUtils'; -import {createAndOpenSearchTransactionThread, getSections, getSortedSections, getValidGroupBy} from '@libs/SearchUIUtils'; +import {createAndOpenSearchTransactionThread, getSections, getSortedSections, getValidGroupBy, isCreatedDateType} from '@libs/SearchUIUtils'; import {isDeletedTransaction} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; @@ -294,6 +294,7 @@ function SearchStaticList({ shouldShowCheckbox={canSelectMultiple} shouldShowErrors violations={item.violations} + isDateColumnCreated={isCreatedDateType(type)} dateColumnSize={CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} amountColumnSize={CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} taxAmountColumnSize={CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} diff --git a/src/components/Search/SearchTableHeader.tsx b/src/components/Search/SearchTableHeader.tsx index dce0eb64d31c..4ac8d0bbc1f1 100644 --- a/src/components/Search/SearchTableHeader.tsx +++ b/src/components/Search/SearchTableHeader.tsx @@ -30,7 +30,9 @@ type SearchHeaderIcons = { Bank?: IconAsset; }; -const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [ +// `getExpenseHeaders` is shared by expense, invoice and trip Search plus the opened single-report table. +// Invoice is treated like a report, so it labels the date column "Created". Expense/trip and the opened report keep "Date". +const getExpenseHeaders = (groupBy?: SearchGroupBy, isDateColumnCreated = false): SearchColumnConfig[] => [ { columnName: CONST.SEARCH.TABLE_COLUMNS.RECEIPT, translationKey: 'common.receipt', @@ -43,7 +45,7 @@ const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [ }, { columnName: CONST.SEARCH.TABLE_COLUMNS.DATE, - translationKey: 'common.date', + translationKey: isDateColumnCreated ? 'search.filters.created' : 'common.date', canEdit: true, }, { @@ -246,7 +248,7 @@ const getExpenseReportHeaders = (profileIcon?: IconAsset): SearchColumnConfig[] }, { columnName: CONST.SEARCH.TABLE_COLUMNS.DATE, - translationKey: 'common.date', + translationKey: 'search.filters.created', }, { columnName: CONST.SEARCH.TABLE_COLUMNS.SUBMITTED, @@ -501,7 +503,7 @@ function getSearchColumns(type: ValueOf, icons: } return getExpenseHeaders(groupBy); case CONST.SEARCH.DATA_TYPES.INVOICE: - return getExpenseHeaders(groupBy); + return getExpenseHeaders(groupBy, true); case CONST.SEARCH.DATA_TYPES.TRIP: return getExpenseHeaders(groupBy); case CONST.SEARCH.DATA_TYPES.TASK: @@ -618,6 +620,7 @@ function SearchTableHeader({ void; shouldRemoveTotalColumnFlex?: boolean; isActionColumnWide?: boolean; + + /** True when the date column renders "Created" (expense reports), which needs a wider column. */ + isDateColumnCreated?: boolean; }; function SortableTableHeader({ @@ -51,6 +54,7 @@ function SortableTableHeader({ sortOrder, shouldShowColumn, dateColumnSize, + isDateColumnCreated, submittedColumnSize, approvedColumnSize, postedColumnSize, @@ -103,6 +107,7 @@ function SortableTableHeader({ containerStyle={[ StyleUtils.getReportTableColumnStyles(columnName, { isDateColumnWide: dateColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, + isDateColumnCreated, isSubmittedColumnWide: submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, isApprovedColumnWide: approvedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, isPostedColumnWide: postedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, diff --git a/src/components/TransactionItemRow/TransactionItemRowWide.tsx b/src/components/TransactionItemRow/TransactionItemRowWide.tsx index 696c27521921..47562e5f12eb 100644 --- a/src/components/TransactionItemRow/TransactionItemRowWide.tsx +++ b/src/components/TransactionItemRow/TransactionItemRowWide.tsx @@ -80,6 +80,7 @@ function TransactionItemRowWide({ isSelected, shouldShowTooltip, dateColumnSize, + isDateColumnCreated = false, submittedColumnSize, approvedColumnSize, postedColumnSize, @@ -221,7 +222,7 @@ function TransactionItemRowWide({ return ( > = {}; for (const column of columnsToExport) { - exportColumnLabels[column] = translate(getSearchColumnTranslationKey(column)); + exportColumnLabels[column] = translate(getSearchColumnTranslationKey(column, exportSearchType)); } const jsonQuery = queryJSONToExport diff --git a/src/languages/de.ts b/src/languages/de.ts index 61ef3d80d840..41c65a507301 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -9185,6 +9185,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc paid: 'Bezahlt', exported: 'Exportiert', posted: 'Gebucht', + created: 'Erstellt', + createdDate: 'Erstellungsdatum', withdrawn: 'Zurückgezogen', billable: 'Abrechenbar', reimbursable: 'Erstattungsfähig', diff --git a/src/languages/el.ts b/src/languages/el.ts index b8b50589d867..6b69fd2d9218 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -9409,6 +9409,8 @@ ${reportName}`, paid: 'Πληρωμένο', exported: 'Έγινε εξαγωγή', posted: 'Καταχωρισμένο', + created: 'Δημιουργήθηκε', + createdDate: 'Ημερομηνία δημιουργίας', withdrawn: 'Ανακλήθηκε', billable: 'Χρεώσιμη', reimbursable: 'Επανεντάξιμο', diff --git a/src/languages/en.ts b/src/languages/en.ts index d7231cfad18a..7bbaf788f16f 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -9327,6 +9327,8 @@ const translations = { paid: 'Paid', exported: 'Exported', posted: 'Posted', + created: 'Created', + createdDate: 'Created date', withdrawn: 'Withdrawn', billable: 'Billable', reimbursable: 'Reimbursable', diff --git a/src/languages/es.ts b/src/languages/es.ts index f73e04d2b449..974c04b6a0d4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -9046,6 +9046,8 @@ ${reportName}`, paid: 'Pago', exported: 'Exportación', posted: 'Contabilización', + created: 'Creado', + createdDate: 'Fecha de creación', withdrawn: 'Retirada', billable: 'Facturable', reimbursable: 'Reembolsable', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index a9e4c7406e96..f843ee731e0d 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -9217,6 +9217,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e paid: 'Payé', exported: 'Exporté', posted: 'Publié', + created: 'Créé', + createdDate: 'Date de création', withdrawn: 'Retiré', billable: 'Facturable', reimbursable: 'Remboursable', diff --git a/src/languages/it.ts b/src/languages/it.ts index dfd914da2d1a..0b5062f03ec7 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -9156,6 +9156,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, paid: 'Pagato', exported: 'Esportato', posted: 'Pubblicato', + created: 'Creato', + createdDate: 'Data di creazione', withdrawn: 'Ritirata', billable: 'Fatturabile', reimbursable: 'Rimborsabile', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index fa68df8e33e5..f78583d3c5dc 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -9039,6 +9039,8 @@ ${reportName}`, paid: '支払い済み', exported: 'エクスポート済み', posted: '投稿日', + created: '作成', + createdDate: '作成日', withdrawn: '取下済み', billable: '請求可能', reimbursable: '払い戻し対象', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index fd7a2d1f1b92..4a80c3439125 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -9124,6 +9124,8 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, paid: 'Betaald', exported: 'Geëxporteerd', posted: 'Gepost', + created: 'Aangemaakt', + createdDate: 'Aanmaakdatum', withdrawn: 'Ingetrokken', billable: 'Factureerbaar', reimbursable: 'Vergoedbaar', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 46a58419b1be..4e4510fb37dd 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -9103,6 +9103,8 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, paid: 'Zapłacono', exported: 'Wyeksportowano', posted: 'Opublikowano', + created: 'Utworzono', + createdDate: 'Data utworzenia', withdrawn: 'Wycofano', billable: 'Fakturowalne', reimbursable: 'Podlegające zwrotowi', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 7035c3e6cc21..dcfb6e134d16 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -9122,6 +9122,8 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, paid: 'Pago', exported: 'Exportado', posted: 'Publicado', + created: 'Criado', + createdDate: 'Data de criação', withdrawn: 'Retirado', billable: 'Faturável', reimbursable: 'Reembolsável', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index abff3020d28c..dccfb97b1123 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -8801,6 +8801,8 @@ ${reportName}`, paid: '已支付', exported: '已导出', posted: '已发布', + created: '创建', + createdDate: '创建日期', withdrawn: '已撤回', billable: '可计费', reimbursable: '可报销', diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 8bf5ac1a6fac..fd11218ccaff 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -4582,12 +4582,20 @@ function getCustomColumnDefault(value?: SearchDataTypes | SearchGroupBy): Search } } -function getSearchColumnTranslationKey(column: SearchSortBy): TranslationPaths { +/** + * The date column/filter reads "Created"/"Created date" only for report-style types: expense report and invoice + * (invoice is treated like a report). Every other type (expense, trip, chat, task) keeps "Date". + */ +function isCreatedDateType(type?: SearchDataTypes): boolean { + return type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT || type === CONST.SEARCH.DATA_TYPES.INVOICE; +} + +function getSearchColumnTranslationKey(column: SearchSortBy, type?: SearchDataTypes): TranslationPaths { switch (column) { case CONST.SEARCH.TABLE_COLUMNS.AVATAR: return 'common.avatar'; case CONST.SEARCH.TABLE_COLUMNS.DATE: - return 'common.date'; + return isCreatedDateType(type) ? 'search.filters.created' : 'common.date'; case CONST.SEARCH.TABLE_COLUMNS.SUBMITTED: return 'common.submitted'; case CONST.SEARCH.TABLE_COLUMNS.APPROVED: @@ -5148,11 +5156,11 @@ function getTypeOptions(translate: LocalizedTranslate, policies: OnyxCollection< return shouldHideInvoiceOption ? typeOptions.filter((typeOption) => typeOption.value !== CONST.SEARCH.DATA_TYPES.INVOICE) : typeOptions; } -function getSortByOptions(columns: SearchColumnType[], translate: LocalizedTranslate) { +function getSortByOptions(columns: SearchColumnType[], translate: LocalizedTranslate, type?: SearchDataTypes) { const sortableColumns: Array> = []; for (const column of columns) { if (isColumnSortable(column)) { - sortableColumns.push({text: translate(getSearchColumnTranslationKey(column)), value: getSortByForColumn(column)}); + sortableColumns.push({text: translate(getSearchColumnTranslationKey(column, type)), value: getSortByForColumn(column)}); } } return sortableColumns; @@ -5863,6 +5871,17 @@ function isMappedFilterKey(key: string): key is MappedFilterKey { return hasKey(FILTER_VIEW_MAP, removeNegation(key)); } +/** + * Returns the label key for a filter, accounting for the active Search type. + * The date filter reads "Created date" only for report-style types (expense report, invoice). Every other type keeps its base label ("Date"). + */ +function getFilterViewLabelKey(filterKey: keyof typeof FILTER_VIEW_MAP, type?: SearchDataTypes): TranslationPaths { + if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE && isCreatedDateType(type)) { + return 'search.filters.createdDate'; + } + return FILTER_VIEW_MAP[filterKey].labelKey; +} + function mapFiltersFormToLabelValueList( searchAdvancedFiltersForm: Partial, skipFilters: Set | undefined, @@ -5909,7 +5928,7 @@ function mapFiltersFormToLabelValueList( const displayValue = isAmountFilterKey(syntax) ? getAmountDisplayValue(syntax, searchAdvancedFiltersForm, translate, convertToDisplayStringWithoutCurrency) : getDateDisplayValue(syntax, searchAdvancedFiltersForm, translate, dateFnsLocale); - const label = FILTER_VIEW_MAP[syntax].labelKey; + const label = getFilterViewLabelKey(syntax, type); if (displayValue && label) { addedGroups.add(syntax); @@ -6690,7 +6709,8 @@ function getTableMinWidth(columns: SearchColumnType[], type?: SearchDataTypes, i } else if (column === CONST.SEARCH.TABLE_COLUMNS.ACTION) { minWidth += (isActionColumnWide ?? type === CONST.SEARCH.DATA_TYPES.TASK) ? 80 : 68; } else if (column === CONST.SEARCH.TABLE_COLUMNS.DATE) { - minWidth += 48; + // Only the report-style "Created" header (expense report, invoice) needs the sibling date-column room ("Submitted"/"Approved"). Other types keep "Date" narrower. + minWidth += isCreatedDateType(type) ? 72 : 48; } else if ( column === CONST.SEARCH.TABLE_COLUMNS.SUBMITTED || column === CONST.SEARCH.TABLE_COLUMNS.APPROVED || @@ -6989,6 +7009,8 @@ export { MONTHLY_ACCRUAL_SEARCH_KEYS, RECONCILIATION_SEARCH_KEYS, FILTER_VIEW_MAP, + getFilterViewLabelKey, + isCreatedDateType, doesSearchItemMatchSort, isPolicyEligibleForSpendOverTime, hasFlexColumn, diff --git a/src/pages/Search/SearchAdvancedFiltersContentPage/SearchAdvancedFiltersContentBase.tsx b/src/pages/Search/SearchAdvancedFiltersContentPage/SearchAdvancedFiltersContentBase.tsx index 8a31225995c7..258b8b42af3c 100644 --- a/src/pages/Search/SearchAdvancedFiltersContentPage/SearchAdvancedFiltersContentBase.tsx +++ b/src/pages/Search/SearchAdvancedFiltersContentPage/SearchAdvancedFiltersContentBase.tsx @@ -9,7 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SearchAdvancedFiltersParamList} from '@libs/Navigation/types'; -import {FILTER_VIEW_MAP} from '@libs/SearchUIUtils'; +import {FILTER_VIEW_MAP, getFilterViewLabelKey} from '@libs/SearchUIUtils'; import type {SearchFilter} from '@libs/SearchUIUtils'; import {SearchAdvancedFiltersActionContext, SearchAdvancedFiltersContext} from '@pages/Search/SearchAdvancedFiltersProvider'; @@ -58,7 +58,7 @@ function SearchAdvancedFiltersContentBase() { validFilterKey ? ( <> diff --git a/src/pages/Search/SearchColumnsPage.tsx b/src/pages/Search/SearchColumnsPage.tsx index eb4f90065b5e..ed3c1901f0e5 100644 --- a/src/pages/Search/SearchColumnsPage.tsx +++ b/src/pages/Search/SearchColumnsPage.tsx @@ -69,6 +69,7 @@ function SearchColumnsPage() { groupColumns={allGroupCustomColumns} defaultGroupColumns={defaultGroupCustomColumns} onSave={applyChanges} + type={queryType} /> ); } diff --git a/src/pages/Search/SearchSavePage.tsx b/src/pages/Search/SearchSavePage.tsx index 15f5f3a46c5d..0b84b83ff75b 100644 --- a/src/pages/Search/SearchSavePage.tsx +++ b/src/pages/Search/SearchSavePage.tsx @@ -130,7 +130,7 @@ function getAppliedDisplays(searchAdvancedFiltersForm: Partial col === defaultCustomColumns.at(index)); if (!isDefaultState) { - appliedDisplays.push({label: translate('search.columns'), value: columns.map((column) => translate(getSearchColumnTranslationKey(column))).join(', ')}); + appliedDisplays.push({label: translate('search.columns'), value: columns.map((column) => translate(getSearchColumnTranslationKey(column, queryType))).join(', ')}); } } diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 8d2d6ee0bf32..1ae85ac8c180 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -59,6 +59,7 @@ import splitPercentageInputStyles from './splitPercentageInputStyles'; type GetReportTableColumnStylesParams = { isDateColumnWide?: boolean; + isDateColumnCreated?: boolean; isAmountColumnWide?: boolean; isTaxAmountColumnWide?: boolean; isSubmittedColumnWide?: boolean; @@ -1936,6 +1937,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ isPostedColumnWide, isExportedColumnWide, isDateColumnWide, + isDateColumnCreated, isTaxAmountColumnWide, isAmountColumnWide, shouldRemoveTotalColumnFlex, @@ -1994,11 +1996,14 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ case CONST.SEARCH.TABLE_COLUMNS.EXPORTED: columnWidth = {...getWidthStyle(isExportedColumnWide ? variables.w102 : variables.w62)}; break; - case CONST.SEARCH.TABLE_COLUMNS.DATE: + case CONST.SEARCH.TABLE_COLUMNS.DATE: { + // "Created" (expense-report) needs the wider w80. "Date" (expense/task/single report) keeps w62. + const normalDateWidth = isDateColumnCreated ? variables.w80 : variables.w62; columnWidth = { - ...getWidthStyle(isDateColumnWide ? variables.w102 : variables.w62), + ...getWidthStyle(isDateColumnWide ? variables.w102 : normalDateWidth), }; break; + } case CONST.SEARCH.TABLE_COLUMNS.WITHDRAWN: case CONST.SEARCH.TABLE_COLUMNS.GROUP_WITHDRAWN: columnWidth = { diff --git a/tests/unit/Search/CreatedDateScopedLabelsTest.ts b/tests/unit/Search/CreatedDateScopedLabelsTest.ts new file mode 100644 index 000000000000..a0be59a8aa53 --- /dev/null +++ b/tests/unit/Search/CreatedDateScopedLabelsTest.ts @@ -0,0 +1,91 @@ +import {getExpenseHeaders} from '@components/Search/SearchTableHeader'; + +import {FILTER_VIEW_MAP, getFilterViewLabelKey, getSearchColumnTranslationKey, getTableMinWidth, isCreatedDateType} from '@libs/SearchUIUtils'; + +import CONST from '@src/CONST'; + +/** + * Issue #98148: the "Created" / "Created date" rename is scoped to report-style Search types only. + * `type:expense-report` and `type:invoice` (invoice is treated like a report). Every other Search type + * (expense, trip, chat, task) and the opened single-report table (which reuses getExpenseHeaders) keep + * "Date". These tests lock that scoping in on the column label, the filter label and the column width so + * a future edit can't silently widen it to every type or drop invoice back to "Date". + */ +describe('Created date scoped labels (#98148)', () => { + const DATE = CONST.SEARCH.TABLE_COLUMNS.DATE; + const {EXPENSE, EXPENSE_REPORT, TASK, TRIP, INVOICE} = CONST.SEARCH.DATA_TYPES; + + describe('isCreatedDateType (shared predicate)', () => { + it('is true only for the report-style types', () => { + expect(isCreatedDateType(EXPENSE_REPORT)).toBe(true); + expect(isCreatedDateType(INVOICE)).toBe(true); + }); + + it('is false for expense, trip, task and no type', () => { + expect(isCreatedDateType(EXPENSE)).toBe(false); + expect(isCreatedDateType(TRIP)).toBe(false); + expect(isCreatedDateType(TASK)).toBe(false); + expect(isCreatedDateType()).toBe(false); + }); + }); + + describe('getSearchColumnTranslationKey (column header, Sort by, Edit columns, saved search, CSV current view)', () => { + it('returns "Created" for the DATE column in expense-report and invoice search', () => { + expect(getSearchColumnTranslationKey(DATE, EXPENSE_REPORT)).toBe('search.filters.created'); + expect(getSearchColumnTranslationKey(DATE, INVOICE)).toBe('search.filters.created'); + }); + + it('keeps "Date" for the DATE column in expense, trip and task', () => { + expect(getSearchColumnTranslationKey(DATE, EXPENSE)).toBe('common.date'); + expect(getSearchColumnTranslationKey(DATE, TRIP)).toBe('common.date'); + expect(getSearchColumnTranslationKey(DATE, TASK)).toBe('common.date'); + }); + + it('defaults to "Date" when no type is passed (e.g. the opened single-report table)', () => { + expect(getSearchColumnTranslationKey(DATE)).toBe('common.date'); + }); + }); + + describe('getFilterViewLabelKey (filter menu row, applied chip, filter subpage title)', () => { + it('returns "Created date" for the DATE filter in expense-report and invoice search', () => { + expect(getFilterViewLabelKey(CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, EXPENSE_REPORT)).toBe('search.filters.createdDate'); + expect(getFilterViewLabelKey(CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, INVOICE)).toBe('search.filters.createdDate'); + }); + + it('keeps "Date" for the DATE filter in expense, trip and with no type', () => { + expect(getFilterViewLabelKey(CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, EXPENSE)).toBe('common.date'); + expect(getFilterViewLabelKey(CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, TRIP)).toBe('common.date'); + expect(getFilterViewLabelKey(CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE)).toBe('common.date'); + }); + + it('does not touch non-date filters, even in report-style search', () => { + expect(getFilterViewLabelKey(CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED, EXPENSE_REPORT)).toBe(FILTER_VIEW_MAP[CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED].labelKey); + }); + }); + + describe('column builders (getExpenseHeaders is shared by expense, invoice, trip and the opened report)', () => { + it('keeps the DATE header on "Date" by default (expense/trip and the opened single-report table)', () => { + const dateColumn = getExpenseHeaders().find((column) => column.columnName === DATE); + expect(dateColumn?.translationKey).toBe('common.date'); + }); + + it('labels the DATE header "Created" when built for the report-style (invoice) variant', () => { + const dateColumn = getExpenseHeaders(undefined, true).find((column) => column.columnName === DATE); + expect(dateColumn?.translationKey).toBe('search.filters.created'); + }); + }); + + describe('getTableMinWidth (horizontal-scroll budget)', () => { + it('gives the DATE column the wider "Created" budget in expense-report and invoice search', () => { + const expenseWidth = getTableMinWidth([DATE], EXPENSE); + expect(getTableMinWidth([DATE], EXPENSE_REPORT)).toBeGreaterThan(expenseWidth); + expect(getTableMinWidth([DATE], INVOICE)).toBeGreaterThan(expenseWidth); + }); + + it('keeps the narrower "Date" budget for trip and task', () => { + const expenseWidth = getTableMinWidth([DATE], EXPENSE); + expect(getTableMinWidth([DATE], TRIP)).toBe(expenseWidth); + expect(getTableMinWidth([DATE], TASK)).toBe(expenseWidth); + }); + }); +});