From 1c35d23a147d80fe7d6d1c5d915a2f2494141565 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 13:12:55 -0700 Subject: [PATCH 01/17] add exported date --- src/CONST/index.ts | 3 +++ src/libs/SearchUIUtils.ts | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 71ab52c96754..d23b1515d0e5 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -6698,6 +6698,7 @@ const CONST = { SUBMITTED: this.TABLE_COLUMNS.SUBMITTED, APPROVED: this.TABLE_COLUMNS.APPROVED, POSTED: this.TABLE_COLUMNS.POSTED, + EXPORTED: this.TABLE_COLUMNS.EXPORTED, MERCHANT: this.TABLE_COLUMNS.MERCHANT, FROM: this.TABLE_COLUMNS.FROM, TO: this.TABLE_COLUMNS.TO, @@ -6718,6 +6719,7 @@ const CONST = { DATE: this.TABLE_COLUMNS.DATE, SUBMITTED: this.TABLE_COLUMNS.SUBMITTED, APPROVED: this.TABLE_COLUMNS.APPROVED, + EXPORTED: this.TABLE_COLUMNS.EXPORTED, STATUS: this.TABLE_COLUMNS.STATUS, TITLE: this.TABLE_COLUMNS.TITLE, FROM: this.TABLE_COLUMNS.FROM, @@ -6800,6 +6802,7 @@ const CONST = { SUBMITTED: 'submitted', APPROVED: 'approved', POSTED: 'posted', + EXPORTED: 'exported', MERCHANT: 'merchant', DESCRIPTION: 'description', FROM: 'from', diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index e515e2d7da95..509b9f5ec7c0 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -867,6 +867,7 @@ type ShouldShowYearResult = { shouldShowYearSubmitted: boolean; shouldShowYearApproved: boolean; shouldShowYearPosted: boolean; + shouldShowYearExported: boolean; }; /** @@ -884,8 +885,9 @@ function shouldShowYear( shouldShowYearSubmitted: false, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, }; - + console.log('over here item') const currentYear = new Date().getFullYear(); if (Array.isArray(data)) { @@ -908,6 +910,7 @@ function shouldShowYear( } } if (isTransactionListItemType(item)) { + console.log('over here item 1', item); const transactionCreated = getTransactionCreatedDate(item); if (transactionCreated && DateUtils.doesDateBelongToAPastYear(transactionCreated)) { result.shouldShowYearCreated = true; @@ -937,6 +940,7 @@ function shouldShowYear( for (const key of Object.keys(data)) { if (!checkOnlyReports && isTransactionEntry(key)) { const item = data[key]; + console.log('over here item 2', item); if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { result.shouldShowYearCreated = true; } @@ -963,6 +967,7 @@ function shouldShowYear( } } else if (isReportEntry(key)) { const item = data[key]; + console.log('over here item 3', item); if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { result.shouldShowYearCreated = true; From 37f5c6c4e328b67961a5d859773fcc76110230c8 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 13:37:49 -0700 Subject: [PATCH 02/17] add exported logic --- src/libs/SearchUIUtils.ts | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 509b9f5ec7c0..7b8377f7f17c 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -887,7 +887,7 @@ function shouldShowYear( shouldShowYearPosted: false, shouldShowYearExported: false, }; - console.log('over here item') + const currentYear = new Date().getFullYear(); if (Array.isArray(data)) { @@ -910,7 +910,6 @@ function shouldShowYear( } } if (isTransactionListItemType(item)) { - console.log('over here item 1', item); const transactionCreated = getTransactionCreatedDate(item); if (transactionCreated && DateUtils.doesDateBelongToAPastYear(transactionCreated)) { result.shouldShowYearCreated = true; @@ -940,7 +939,7 @@ function shouldShowYear( for (const key of Object.keys(data)) { if (!checkOnlyReports && isTransactionEntry(key)) { const item = data[key]; - console.log('over here item 2', item); + if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { result.shouldShowYearCreated = true; } @@ -957,6 +956,16 @@ function shouldShowYear( const postedYear = parseInt(item.posted.slice(0, 4), 10); result.shouldShowYearPosted = postedYear !== currentYear; } + + const reportActions = data[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item.reportID}`]; + const exportedAction = Object.values(reportActions) + .filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) + .at(0); + + if (exportedAction?.created && DateUtils.doesDateBelongToAPastYear(exportedAction.created)) { + result.shouldShowYearExported = true; + } } else if (!checkOnlyReports && isReportActionEntry(key)) { const item = data[key]; for (const action of Object.values(item)) { @@ -967,7 +976,6 @@ function shouldShowYear( } } else if (isReportEntry(key)) { const item = data[key]; - console.log('over here item 3', item); if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { result.shouldShowYearCreated = true; @@ -978,10 +986,22 @@ function shouldShowYear( if (item.approved && DateUtils.doesDateBelongToAPastYear(item.approved)) { result.shouldShowYearApproved = true; } + + const reportActions = data[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item.reportID}`]; + if (reportActions) { + const exportedAction = Object.values(reportActions) + .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) + .at(0); + + if (exportedAction?.created && DateUtils.doesDateBelongToAPastYear(exportedAction.created)) { + result.shouldShowYearExported = true; + } + } } // Early exit if all flags are true - if (result.shouldShowYearCreated && result.shouldShowYearSubmitted && result.shouldShowYearApproved && result.shouldShowYearPosted) { + if (result.shouldShowYearCreated && result.shouldShowYearSubmitted && result.shouldShowYearApproved && result.shouldShowYearPosted && result.shouldShowYearExported) { return result; } } From 88b1284030171f7ade45fd2faa3b6e9d75fc9d49 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 13:42:50 -0700 Subject: [PATCH 03/17] use map for efficient lookup --- src/libs/SearchUIUtils.ts | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 7b8377f7f17c..fbceb1626f52 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -936,6 +936,21 @@ function shouldShowYear( return result; } + const lastExportedActionByReportID = new Map(); + for (const key of Object.keys(data)) { + if (isReportActionEntry(key)) { + const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); + const actions = data[key]; + const exportedAction = Object.values(actions) + .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) + .at(0); + if (exportedAction) { + lastExportedActionByReportID.set(reportID, exportedAction); + } + } + } + for (const key of Object.keys(data)) { if (!checkOnlyReports && isTransactionEntry(key)) { const item = data[key]; @@ -957,12 +972,7 @@ function shouldShowYear( result.shouldShowYearPosted = postedYear !== currentYear; } - const reportActions = data[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item.reportID}`]; - const exportedAction = Object.values(reportActions) - .filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) - .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) - .at(0); - + const exportedAction = lastExportedActionByReportID.get(item.reportID); if (exportedAction?.created && DateUtils.doesDateBelongToAPastYear(exportedAction.created)) { result.shouldShowYearExported = true; } @@ -987,16 +997,9 @@ function shouldShowYear( result.shouldShowYearApproved = true; } - const reportActions = data[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item.reportID}`]; - if (reportActions) { - const exportedAction = Object.values(reportActions) - .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) - .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) - .at(0); - - if (exportedAction?.created && DateUtils.doesDateBelongToAPastYear(exportedAction.created)) { - result.shouldShowYearExported = true; - } + const exportedAction = lastExportedActionByReportID.get(item.reportID ?? ''); + if (exportedAction?.created && DateUtils.doesDateBelongToAPastYear(exportedAction.created)) { + result.shouldShowYearExported = true; } } From 1d549f7560aeaedccac474d1ea5c7ccd7f068194 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 13:51:54 -0700 Subject: [PATCH 04/17] add more logic --- .../Search/ExpenseReportListItemRow.tsx | 10 ++++++++++ .../Search/TransactionListItem.tsx | 5 ++++- .../SearchTableHeader.tsx | 11 +++++++++++ .../SortableTableHeader.tsx | 3 +++ .../SelectionListWithSections/types.ts | 15 +++++++++++++-- src/components/TransactionItemRow/index.tsx | 17 +++++++++++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx index 28faa0dc1e88..33ef9a7fadd1 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx @@ -107,6 +107,16 @@ function ExpenseReportListItemRow({ /> ), + [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: ( + + + + ), [CONST.SEARCH.TABLE_COLUMNS.STATUS]: ( ({ backgroundColor: theme.highlightBG, }); - const {amountColumnSize, dateColumnSize, taxAmountColumnSize, submittedColumnSize, approvedColumnSize, postedColumnSize} = useMemo(() => { + const {amountColumnSize, dateColumnSize, taxAmountColumnSize, submittedColumnSize, approvedColumnSize, postedColumnSize, exportedColumnSize} = useMemo(() => { return { amountColumnSize: transactionItem.isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, taxAmountColumnSize: transactionItem.isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, @@ -105,6 +105,7 @@ function TransactionListItem({ submittedColumnSize: transactionItem.shouldShowYearSubmitted ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, approvedColumnSize: transactionItem.shouldShowYearApproved ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, postedColumnSize: transactionItem.shouldShowYearPosted ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, + exportedColumnSize: transactionItem.shouldShowYearExported ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL, }; }, [ transactionItem.isAmountColumnWide, @@ -113,6 +114,7 @@ function TransactionListItem({ transactionItem.shouldShowYearSubmitted, transactionItem.shouldShowYearApproved, transactionItem.shouldShowYearPosted, + transactionItem.shouldShowYearExported, ]); const transactionViolations = useMemo(() => { @@ -196,6 +198,7 @@ function TransactionListItem({ submittedColumnSize={submittedColumnSize} approvedColumnSize={approvedColumnSize} postedColumnSize={postedColumnSize} + exportedColumnSize={exportedColumnSize} amountColumnSize={amountColumnSize} taxAmountColumnSize={taxAmountColumnSize} shouldShowCheckbox={!!canSelectMultiple} diff --git a/src/components/SelectionListWithSections/SearchTableHeader.tsx b/src/components/SelectionListWithSections/SearchTableHeader.tsx index 91dcce8a4f9e..521f91d23640 100644 --- a/src/components/SelectionListWithSections/SearchTableHeader.tsx +++ b/src/components/SelectionListWithSections/SearchTableHeader.tsx @@ -46,6 +46,10 @@ const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [ columnName: CONST.SEARCH.TABLE_COLUMNS.POSTED, translationKey: 'search.filters.posted', }, + { + columnName: CONST.SEARCH.TABLE_COLUMNS.EXPORTED, + translationKey: 'search.filters.exported', + }, { columnName: CONST.SEARCH.TABLE_COLUMNS.MERCHANT, translationKey: 'common.merchant', @@ -196,6 +200,10 @@ const getExpenseReportHeaders = (profileIcon?: IconAsset): SearchColumnConfig[] columnName: CONST.SEARCH.TABLE_COLUMNS.APPROVED, translationKey: 'search.filters.approved', }, + { + columnName: CONST.SEARCH.TABLE_COLUMNS.EXPORTED, + translationKey: 'search.filters.exported', + }, { columnName: CONST.SEARCH.TABLE_COLUMNS.STATUS, translationKey: 'common.status', @@ -259,6 +267,7 @@ type SearchTableHeaderProps = { shouldShowYearSubmitted?: boolean; shouldShowYearApproved?: boolean; shouldShowYearPosted?: boolean; + shouldShowYearExported?: boolean; isAmountColumnWide: boolean; isTaxAmountColumnWide: boolean; shouldShowSorting: boolean; @@ -277,6 +286,7 @@ function SearchTableHeader({ shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, + shouldShowYearExported, shouldShowSorting, canSelectMultiple, isAmountColumnWide, @@ -318,6 +328,7 @@ function SearchTableHeader({ submittedColumnSize={shouldShowYearSubmitted ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} approvedColumnSize={shouldShowYearApproved ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} postedColumnSize={shouldShowYearPosted ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} + exportedColumnSize={shouldShowYearExported ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} amountColumnSize={isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} taxAmountColumnSize={isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} shouldShowSorting={shouldShowSorting} diff --git a/src/components/SelectionListWithSections/SortableTableHeader.tsx b/src/components/SelectionListWithSections/SortableTableHeader.tsx index 6cd64a675137..b5f883903673 100644 --- a/src/components/SelectionListWithSections/SortableTableHeader.tsx +++ b/src/components/SelectionListWithSections/SortableTableHeader.tsx @@ -28,6 +28,7 @@ type SearchTableHeaderProps = { submittedColumnSize?: TableColumnSize; approvedColumnSize?: TableColumnSize; postedColumnSize?: TableColumnSize; + exportedColumnSize?: TableColumnSize; amountColumnSize: TableColumnSize; taxAmountColumnSize: TableColumnSize; containerStyles?: StyleProp; @@ -45,6 +46,7 @@ function SortableTableHeader({ submittedColumnSize, approvedColumnSize, postedColumnSize, + exportedColumnSize, containerStyles, shouldShowSorting, onSortPress, @@ -86,6 +88,7 @@ function SortableTableHeader({ submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, approvedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, postedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, + exportedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, ), ]} isSortable={isSortable} diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts index 181c703965d0..790a795b572c 100644 --- a/src/components/SelectionListWithSections/types.ts +++ b/src/components/SelectionListWithSections/types.ts @@ -249,8 +249,8 @@ type TransactionListItemType = ListItem & /** The date the report was approved */ approved?: string; - /** The date the report was posted */ - posted?: string; + /** The date the report was exported */ + exported?: string; /** Policy to which the transaction belongs */ policy: Policy | undefined; @@ -311,6 +311,11 @@ type TransactionListItemType = ListItem & */ shouldShowYearPosted: boolean; + /** Whether we should show the year for the exported date. + * This is true if at least one transaction in the dataset was exported in past years + */ + shouldShowYearExported: boolean; + isAmountColumnWide: boolean; isTaxAmountColumnWide: boolean; @@ -444,6 +449,12 @@ type TransactionReportGroupListItemType = TransactionGroupListItemType & {groupe */ shouldShowYearApproved: boolean; + /** + * Whether we should show the year for the exported date. + * This is true if at least one report in the dataset was exported in past years + */ + shouldShowYearExported: boolean; + /** The main action that can be performed for the report */ action: SearchTransactionAction | undefined; diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 51b9a6f69a6c..12c3fc026d28 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -101,6 +101,7 @@ type TransactionItemRowProps = { submittedColumnSize?: TableColumnSize; approvedColumnSize?: TableColumnSize; postedColumnSize?: TableColumnSize; + exportedColumnSize?: TableColumnSize; amountColumnSize: TableColumnSize; taxAmountColumnSize: TableColumnSize; onCheckboxPress?: (transactionID: string) => void; @@ -146,6 +147,7 @@ function TransactionItemRow({ submittedColumnSize, approvedColumnSize, postedColumnSize, + exportedColumnSize, amountColumnSize, taxAmountColumnSize, onCheckboxPress = () => {}, @@ -180,6 +182,7 @@ function TransactionItemRow({ const isSubmittedColumnWide = submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isApprovedColumnWide = approvedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isPostedColumnWide = postedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; + const isExportedColumnWide = exportedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isAmountColumnWide = amountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isTaxAmountColumnWide = taxAmountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; @@ -322,6 +325,19 @@ function TransactionItemRow({ /> ), + [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: ( + + + + ), [CONST.SEARCH.TABLE_COLUMNS.CATEGORY]: ( Date: Wed, 17 Dec 2025 13:56:14 -0700 Subject: [PATCH 05/17] add more logic --- .../Search/ExpenseReportListItemRow.tsx | 2 +- src/components/TransactionItemRow/index.tsx | 3 +-- src/libs/SearchUIUtils.ts | 4 ++++ src/styles/utils/index.ts | 4 ++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx index 33ef9a7fadd1..ea7b896e5d7f 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx @@ -108,7 +108,7 @@ function ExpenseReportListItemRow({ ), [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: ( - + ({ isSubmittedColumnWide = false, isApprovedColumnWide = false, isPostedColumnWide = false, + isExportedColumnWide = false, ): ViewStyle => { let columnWidth; switch (columnName) { @@ -1749,6 +1750,9 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ case CONST.SEARCH.TABLE_COLUMNS.POSTED: columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)}; break; + case CONST.SEARCH.TABLE_COLUMNS.EXPORTED: + columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)}; + break; case CONST.SEARCH.TABLE_COLUMNS.DATE: // We will remove this variable & param, but in a follow up PR. We are duplicating the logic here to "use" the variable // to prevent eslint errors. This will be removed From 2ce44818876dae018e82ca0fd6f60adee170b9c1 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 13:58:42 -0700 Subject: [PATCH 06/17] fix prettier --- src/components/TransactionItemRow/index.tsx | 14 +++++++++++++- src/libs/SearchUIUtils.ts | 5 ++++- src/styles/utils/index.ts | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 8b105d7bfe4f..2b89e8aa189e 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -328,7 +328,19 @@ function TransactionItemRow({ [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: ( action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .filter( + (action): action is OnyxTypes.ReportAction => + action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, + ) .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) .at(0); if (exportedAction) { diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 543bc722a4ea..69daa5c2499e 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1751,8 +1751,8 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)}; break; case CONST.SEARCH.TABLE_COLUMNS.EXPORTED: - columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)}; - break; + columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)}; + break; case CONST.SEARCH.TABLE_COLUMNS.DATE: // We will remove this variable & param, but in a follow up PR. We are duplicating the logic here to "use" the variable // to prevent eslint errors. This will be removed From bdc48aadd2832495f683aa49894a2859b67399bf Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 14:21:31 -0700 Subject: [PATCH 07/17] add exported to items --- .../Search/ExpenseReportListItemRow.tsx | 3 +- .../SelectionListWithSections/types.ts | 3 ++ src/libs/SearchUIUtils.ts | 44 +++++++++++++++++-- src/styles/utils/index.ts | 2 +- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx index ea7b896e5d7f..f60e98c21ef9 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx @@ -110,8 +110,7 @@ function ExpenseReportListItemRow({ [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: ( diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts index 790a795b572c..fbadfb79e0db 100644 --- a/src/components/SelectionListWithSections/types.ts +++ b/src/components/SelectionListWithSections/types.ts @@ -431,6 +431,9 @@ type TransactionReportGroupListItemType = TransactionGroupListItemType & {groupe /** Final and formatted "to" value used for displaying and sorting */ formattedTo?: string; + /** The date the report was exported */ + exported?: string; + /** * Whether we should show the report year. * This is true if at least one report in the dataset was created in past years diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index f565040b3786..5c2be7b35742 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -675,7 +675,6 @@ function getTransactionItemCommonFormattedProperties( const formattedMerchant = merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ? '' : merchant; const submitted = report?.submitted; const approved = report?.approved; - const exported = report?.exported; // Posted date is in the YYYYMMDD format, so we format it to YYYY-MM-DD here since JS's Date constructor interprets it as an invalid date. const posted = !transactionItem?.posted ? '' : `${transactionItem?.posted.slice(0, 4)}-${transactionItem?.posted.slice(4, 6)}-${transactionItem?.posted.slice(6, 8)}`; @@ -687,7 +686,6 @@ function getTransactionItemCommonFormattedProperties( submitted, approved, posted, - exported, formattedTotal, formattedMerchant, }; @@ -1168,7 +1166,7 @@ function getTransactionsSections( isActionLoadingSet: ReadonlySet | undefined, ): [TransactionListItemType[], number] { const shouldShowMerchant = getShouldShowMerchant(data); - const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted} = shouldShowYear(data); + const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported} = shouldShowYear(data); const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(data); // Pre-filter transaction keys to avoid repeated checks @@ -1182,6 +1180,21 @@ function getTransactionsSections( const transactionsSections: TransactionListItemType[] = []; + const lastExportedActionByReportID = new Map(); + for (const key of Object.keys(data)) { + if (isReportActionEntry(key)) { + const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); + const actions = data[key]; + const exportedAction = Object.values(actions) + .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) + .at(0); + if (exportedAction) { + lastExportedActionByReportID.set(reportID, exportedAction); + } + } + } + const queryJSON = getCurrentSearchQueryJSON(); for (const key of transactionKeys) { @@ -1245,11 +1258,13 @@ function getTransactionsSections( submitted, approved, posted, + exported: lastExportedActionByReportID.get(transactionItem.reportID)?.created ?? '', shouldShowMerchant, shouldShowYear: shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, + shouldShowYearExported, isAmountColumnWide: shouldShowAmountInWideColumn, isTaxAmountColumnWide: shouldShowTaxAmountInWideColumn, violations: transactionViolations, @@ -1626,6 +1641,7 @@ function getReportSections( shouldShowYearSubmitted: shouldShowYearSubmittedTransaction, shouldShowYearApproved: shouldShowYearApprovedTransaction, shouldShowYearPosted: shouldShowYearPostedTransaction, + shouldShowYearExported: shouldShowYearExportedTransaction, } = shouldShowYear(data); const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(data); const {moneyRequestReportActionsByTransactionID, holdReportActionsByTransactionID} = createReportActionsLookupMaps(data); @@ -1653,8 +1669,24 @@ function getReportSections( shouldShowYearCreated: shouldShowYearCreatedReport, shouldShowYearSubmitted: shouldShowYearSubmittedReport, shouldShowYearApproved: shouldShowYearApprovedReport, + shouldShowYearExported: shouldShowYearExportedReport, } = shouldShowYear(data, true); + const lastExportedActionByReportID = new Map(); + for (const key of Object.keys(data)) { + if (isReportActionEntry(key)) { + const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); + const actions = data[key]; + const exportedAction = Object.values(actions) + .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) + .at(0); + if (exportedAction) { + lastExportedActionByReportID.set(reportID, exportedAction); + } + } + } + for (const key of orderedKeys) { if (isReportEntry(key) && (data[key].type === CONST.REPORT.TYPE.IOU || data[key].type === CONST.REPORT.TYPE.EXPENSE || data[key].type === CONST.REPORT.TYPE.INVOICE)) { const reportItem = {...data[key]} as OnyxTypes.Report; @@ -1721,6 +1753,7 @@ function getReportSections( groupedBy: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, from: fromDetails, to: toDetails, + exported: lastExportedActionByReportID.get(reportItem.reportID)?.created ?? '', formattedFrom, formattedTo, formattedStatus, @@ -1729,6 +1762,7 @@ function getReportSections( shouldShowYear: shouldShowYearCreatedReport, shouldShowYearSubmitted: shouldShowYearSubmittedReport, shouldShowYearApproved: shouldShowYearApprovedReport, + shouldShowYearExported: shouldShowYearExportedReport, hasVisibleViolations: hasVisibleViolationsForReport, }; @@ -1773,11 +1807,13 @@ function getReportSections( formattedTotal, formattedMerchant, date, + exported: lastExportedActionByReportID.get(transactionItem.reportID)?.created ?? '', shouldShowMerchant, shouldShowYear: shouldShowYearCreatedTransaction, shouldShowYearSubmitted: shouldShowYearSubmittedTransaction, shouldShowYearApproved: shouldShowYearApprovedTransaction, shouldShowYearPosted: shouldShowYearPostedTransaction, + shouldShowYearExported: shouldShowYearExportedTransaction, keyForList: transactionItem.transactionID, violations: transactionViolations, isAmountColumnWide: shouldShowAmountInWideColumn, @@ -2337,6 +2373,8 @@ function getSearchColumnTranslationKey(columnId: SearchCustomColumnIds): Transla return 'search.filters.approved'; case CONST.SEARCH.TABLE_COLUMNS.POSTED: return 'search.filters.posted'; + case CONST.SEARCH.TABLE_COLUMNS.EXPORTED: + return 'search.filters.exported'; case CONST.SEARCH.TABLE_COLUMNS.MERCHANT: return 'common.merchant'; case CONST.SEARCH.TABLE_COLUMNS.FROM: diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 69daa5c2499e..55f161c3e3c6 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1751,7 +1751,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)}; break; case CONST.SEARCH.TABLE_COLUMNS.EXPORTED: - columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)}; + columnWidth = {...getWidthStyle(isExportedColumnWide ? variables.w92 : variables.w72)}; break; case CONST.SEARCH.TABLE_COLUMNS.DATE: // We will remove this variable & param, but in a follow up PR. We are duplicating the logic here to "use" the variable From df83d4739b589c200fa20c04220ecd5b92027982 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 14:24:59 -0700 Subject: [PATCH 08/17] dry code --- src/libs/SearchUIUtils.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 5c2be7b35742..849c4c1f27d7 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1186,7 +1186,10 @@ function getTransactionsSections( const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); const actions = data[key]; const exportedAction = Object.values(actions) - .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .filter( + (action): action is OnyxTypes.ReportAction => + action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, + ) .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) .at(0); if (exportedAction) { @@ -1678,7 +1681,10 @@ function getReportSections( const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); const actions = data[key]; const exportedAction = Object.values(actions) - .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .filter( + (action): action is OnyxTypes.ReportAction => + action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, + ) .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) .at(0); if (exportedAction) { From db7450c7bc0ae8268dc2959fa64cc4bcd57116fa Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 14:26:11 -0700 Subject: [PATCH 09/17] dry code --- src/libs/SearchUIUtils.ts | 76 +++++++++++++-------------------------- 1 file changed, 25 insertions(+), 51 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 849c4c1f27d7..5890f2b8d625 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -872,6 +872,28 @@ type ShouldShowYearResult = { shouldShowYearExported: boolean; }; +/** + * @private + * Builds a map of the last exported action by report ID for efficient lookups + */ +function buildLastExportedActionByReportIDMap(data: OnyxTypes.SearchResults['data']): Map { + const lastExportedActionByReportID = new Map(); + for (const key of Object.keys(data)) { + if (isReportActionEntry(key)) { + const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); + const actions = data[key]; + const exportedAction = Object.values(actions) + .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) + .at(0); + if (exportedAction) { + lastExportedActionByReportID.set(reportID, exportedAction); + } + } + } + return lastExportedActionByReportID; +} + /** * Checks if the date of transactions or reports indicate the need to display the year because they are from a past year. * @param data - The search results data (array or object) @@ -938,23 +960,7 @@ function shouldShowYear( return result; } - const lastExportedActionByReportID = new Map(); - for (const key of Object.keys(data)) { - if (isReportActionEntry(key)) { - const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); - const actions = data[key]; - const exportedAction = Object.values(actions) - .filter( - (action): action is OnyxTypes.ReportAction => - action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, - ) - .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) - .at(0); - if (exportedAction) { - lastExportedActionByReportID.set(reportID, exportedAction); - } - } - } + const lastExportedActionByReportID = buildLastExportedActionByReportIDMap(data); for (const key of Object.keys(data)) { if (!checkOnlyReports && isTransactionEntry(key)) { @@ -1180,23 +1186,7 @@ function getTransactionsSections( const transactionsSections: TransactionListItemType[] = []; - const lastExportedActionByReportID = new Map(); - for (const key of Object.keys(data)) { - if (isReportActionEntry(key)) { - const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); - const actions = data[key]; - const exportedAction = Object.values(actions) - .filter( - (action): action is OnyxTypes.ReportAction => - action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, - ) - .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) - .at(0); - if (exportedAction) { - lastExportedActionByReportID.set(reportID, exportedAction); - } - } - } + const lastExportedActionByReportID = buildLastExportedActionByReportIDMap(data); const queryJSON = getCurrentSearchQueryJSON(); @@ -1675,23 +1665,7 @@ function getReportSections( shouldShowYearExported: shouldShowYearExportedReport, } = shouldShowYear(data, true); - const lastExportedActionByReportID = new Map(); - for (const key of Object.keys(data)) { - if (isReportActionEntry(key)) { - const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); - const actions = data[key]; - const exportedAction = Object.values(actions) - .filter( - (action): action is OnyxTypes.ReportAction => - action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, - ) - .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) - .at(0); - if (exportedAction) { - lastExportedActionByReportID.set(reportID, exportedAction); - } - } - } + const lastExportedActionByReportID = buildLastExportedActionByReportIDMap(data); for (const key of orderedKeys) { if (isReportEntry(key) && (data[key].type === CONST.REPORT.TYPE.IOU || data[key].type === CONST.REPORT.TYPE.EXPENSE || data[key].type === CONST.REPORT.TYPE.INVOICE)) { From bd39d560f5cca6ac896c694b951d2dc177304751 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 14:28:11 -0700 Subject: [PATCH 10/17] fix ts --- src/components/TransactionItemRow/index.tsx | 3 +++ src/libs/SearchUIUtils.ts | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 2b89e8aa189e..e150aff282a7 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -63,6 +63,9 @@ type TransactionWithOptionalSearchFields = TransactionWithOptionalHighlight & { /** The personal details of the user paying the request */ to?: PersonalDetails; + /** The date the report was exported */ + exported?: string; + /** formatted "to" value used for displaying and sorting on Reports page */ formattedTo?: string; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 5890f2b8d625..617fb443e3a9 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -883,7 +883,10 @@ function buildLastExportedActionByReportIDMap(data: OnyxTypes.SearchResults['dat const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); const actions = data[key]; const exportedAction = Object.values(actions) - .filter((action): action is OnyxTypes.ReportAction => action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) + .filter( + (action): action is OnyxTypes.ReportAction => + action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, + ) .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) .at(0); if (exportedAction) { From a330f9e312b96a03e5c105d499936e7857fd67e9 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 14:38:24 -0700 Subject: [PATCH 11/17] add to columns --- src/libs/SearchUIUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 617fb443e3a9..fc4909b95729 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2772,6 +2772,7 @@ function getColumnsToShow( [CONST.SEARCH.TABLE_COLUMNS.DATE]: true, [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false, [CONST.SEARCH.TABLE_COLUMNS.APPROVED]: false, + [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: false, [CONST.SEARCH.TABLE_COLUMNS.STATUS]: true, [CONST.SEARCH.TABLE_COLUMNS.TITLE]: true, [CONST.SEARCH.TABLE_COLUMNS.FROM]: true, @@ -2842,9 +2843,10 @@ function getColumnsToShow( [CONST.SEARCH.TABLE_COLUMNS.RECEIPT]: true, [CONST.SEARCH.TABLE_COLUMNS.TYPE]: true, [CONST.SEARCH.TABLE_COLUMNS.DATE]: true, + [CONST.SEARCH.TABLE_COLUMNS.POSTED]: false, + [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: false, [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false, [CONST.SEARCH.TABLE_COLUMNS.APPROVED]: false, - [CONST.SEARCH.TABLE_COLUMNS.POSTED]: false, [CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: false, [CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]: false, [CONST.SEARCH.TABLE_COLUMNS.FROM]: false, From d1eeac1da3b6c00519598a2ab117ec436b1e81f7 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 15:01:31 -0700 Subject: [PATCH 12/17] fix styles --- src/components/Search/index.tsx | 3 ++- .../Search/ExpenseReportListItemRow.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 91241b5d3404..ce4403358821 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -1041,7 +1041,7 @@ function Search({ navigation.setParams({q: newQuery, rawQuery: undefined}); }; - const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted} = shouldShowYearUtil(searchResults?.data, isExpenseReportType ?? false); + const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported} = shouldShowYearUtil(searchResults?.data, isExpenseReportType ?? false); const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(searchResults?.data); const shouldShowSorting = !validGroupBy; const shouldShowTableHeader = isLargeScreenWidth && !isChat && !validGroupBy; @@ -1075,6 +1075,7 @@ function Search({ shouldShowYearSubmitted={shouldShowYearSubmitted} shouldShowYearApproved={shouldShowYearApproved} shouldShowYearPosted={shouldShowYearPosted} + shouldShowYearExported={shouldShowYearExported} isAmountColumnWide={shouldShowAmountInWideColumn} isTaxAmountColumnWide={shouldShowTaxAmountInWideColumn} shouldShowSorting={shouldShowSorting} diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx index f60e98c21ef9..0d261cf20a1f 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx @@ -108,7 +108,7 @@ function ExpenseReportListItemRow({ ), [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: ( - + Date: Wed, 17 Dec 2025 15:09:54 -0700 Subject: [PATCH 13/17] fix errors --- src/components/SelectionListWithSections/types.ts | 3 +++ src/libs/SearchUIUtils.ts | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts index fbadfb79e0db..8d0bf24b7424 100644 --- a/src/components/SelectionListWithSections/types.ts +++ b/src/components/SelectionListWithSections/types.ts @@ -249,6 +249,9 @@ type TransactionListItemType = ListItem & /** The date the report was approved */ approved?: string; + /** The date the report was posted */ + posted?: string; + /** The date the report was exported */ exported?: string; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index fc4909b95729..76d336ea0e3d 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -968,7 +968,6 @@ function shouldShowYear( for (const key of Object.keys(data)) { if (!checkOnlyReports && isTransactionEntry(key)) { const item = data[key]; - if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { result.shouldShowYearCreated = true; } @@ -1011,7 +1010,7 @@ function shouldShowYear( result.shouldShowYearApproved = true; } - const exportedAction = lastExportedActionByReportID.get(item.reportID ?? ''); + const exportedAction = lastExportedActionByReportID.get(item.reportID); if (exportedAction?.created && DateUtils.doesDateBelongToAPastYear(exportedAction.created)) { result.shouldShowYearExported = true; } From ebbf37de874f4d4eba822f301ec4e6451937dbbe Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 15:16:30 -0700 Subject: [PATCH 14/17] reorder columns --- src/components/Search/index.tsx | 5 ++++- .../SearchTableHeader.tsx | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index ce4403358821..60298e23d144 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -1041,7 +1041,10 @@ function Search({ navigation.setParams({q: newQuery, rawQuery: undefined}); }; - const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported} = shouldShowYearUtil(searchResults?.data, isExpenseReportType ?? false); + const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported} = shouldShowYearUtil( + searchResults?.data, + isExpenseReportType ?? false, + ); const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(searchResults?.data); const shouldShowSorting = !validGroupBy; const shouldShowTableHeader = isLargeScreenWidth && !isChat && !validGroupBy; diff --git a/src/components/SelectionListWithSections/SearchTableHeader.tsx b/src/components/SelectionListWithSections/SearchTableHeader.tsx index 521f91d23640..764bf0972034 100644 --- a/src/components/SelectionListWithSections/SearchTableHeader.tsx +++ b/src/components/SelectionListWithSections/SearchTableHeader.tsx @@ -34,14 +34,6 @@ const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [ columnName: CONST.SEARCH.TABLE_COLUMNS.DATE, translationKey: 'common.date', }, - { - columnName: CONST.SEARCH.TABLE_COLUMNS.SUBMITTED, - translationKey: 'common.submitted', - }, - { - columnName: CONST.SEARCH.TABLE_COLUMNS.APPROVED, - translationKey: 'search.filters.approved', - }, { columnName: CONST.SEARCH.TABLE_COLUMNS.POSTED, translationKey: 'search.filters.posted', @@ -50,6 +42,14 @@ const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [ columnName: CONST.SEARCH.TABLE_COLUMNS.EXPORTED, translationKey: 'search.filters.exported', }, + { + columnName: CONST.SEARCH.TABLE_COLUMNS.SUBMITTED, + translationKey: 'common.submitted', + }, + { + columnName: CONST.SEARCH.TABLE_COLUMNS.APPROVED, + translationKey: 'search.filters.approved', + }, { columnName: CONST.SEARCH.TABLE_COLUMNS.MERCHANT, translationKey: 'common.merchant', From 3764fd0f188e8de6120a5119c16f840138de08f9 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 15:28:40 -0700 Subject: [PATCH 15/17] fix tests --- tests/unit/Search/SearchUIUtilsTest.ts | 30 ++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index a4338dceae28..c60a07ed4ff6 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -776,6 +776,7 @@ const transactionsListItems = [ submitted: undefined, approved: undefined, posted: '', + exported: '', currency: 'USD', date: '2024-12-21', formattedFrom: 'Admin', @@ -803,6 +804,7 @@ const transactionsListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -834,6 +836,7 @@ const transactionsListItems = [ submitted: '2024-12-21 13:05:20', approved: undefined, posted: '', + exported: '', currency: 'USD', date: '2024-12-21', formattedFrom: 'Admin', @@ -860,6 +863,7 @@ const transactionsListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -902,6 +906,7 @@ const transactionsListItems = [ submitted: '2025-03-05', approved: undefined, posted: '', + exported: '', currency: 'VND', hasEReceipt: false, merchant: '(none)', @@ -935,6 +940,7 @@ const transactionsListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, keyForList: '3', isAmountColumnWide: false, isTaxAmountColumnWide: false, @@ -965,6 +971,7 @@ const transactionsListItems = [ submitted: '2025-03-05', approved: undefined, posted: '', + exported: '', currency: 'VND', hasEReceipt: false, merchant: '(none)', @@ -998,6 +1005,7 @@ const transactionsListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, keyForList: '4', isAmountColumnWide: false, isTaxAmountColumnWide: false, @@ -1041,9 +1049,11 @@ const transactionReportGroupListItems = [ policyID: 'A1B2C3', reportID: '123456789', reportName: 'Expense Report #123', + exported: '', shouldShowYear: true, shouldShowYearSubmitted: true, shouldShowYearApproved: false, + shouldShowYearExported: false, stateNum: 0, statusNum: 0, to: emptyPersonalDetails, @@ -1066,6 +1076,7 @@ const transactionReportGroupListItems = [ created: '2024-12-21', currency: 'USD', date: '2024-12-21', + exported: '', formattedFrom: 'Admin', formattedMerchant: 'Expense', formattedTo: '', @@ -1091,6 +1102,7 @@ const transactionReportGroupListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -1118,6 +1130,7 @@ const transactionReportGroupListItems = [ created: '2024-12-21 13:05:20', submitted: '2024-12-21 13:05:20', approved: undefined, + exported: '', currency: 'USD', formattedFrom: 'Admin', formattedStatus: 'Outstanding', @@ -1141,6 +1154,7 @@ const transactionReportGroupListItems = [ shouldShowYear: true, shouldShowYearSubmitted: true, shouldShowYearApproved: false, + shouldShowYearExported: false, stateNum: 1, statusNum: 1, to: { @@ -1166,6 +1180,7 @@ const transactionReportGroupListItems = [ category: '', comment: {comment: ''}, created: '2024-12-21', + exported: '', currency: 'USD', date: '2024-12-21', formattedFrom: 'Admin', @@ -1198,6 +1213,7 @@ const transactionReportGroupListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', @@ -1229,6 +1245,7 @@ const transactionReportGroupListItems = [ created: '2025-03-05 16:34:27', submitted: '2025-03-05', approved: undefined, + exported: '', currency: 'VND', formattedFrom: 'Admin', formattedStatus: 'Outstanding', @@ -1248,6 +1265,7 @@ const transactionReportGroupListItems = [ shouldShowYear: true, shouldShowYearSubmitted: true, shouldShowYearApproved: false, + shouldShowYearExported: false, stateNum: 1, statusNum: 1, total: 4400, @@ -1284,6 +1302,7 @@ const transactionReportGroupListItems = [ category: '', comment: {comment: ''}, created: '2025-03-05', + exported: '', currency: 'VND', hasEReceipt: false, merchant: '(none)', @@ -1317,6 +1336,7 @@ const transactionReportGroupListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, keyForList: '3', isAmountColumnWide: false, isTaxAmountColumnWide: false, @@ -1344,6 +1364,7 @@ const transactionReportGroupListItems = [ category: '', comment: {comment: ''}, created: '2025-03-05', + exported: '', currency: 'VND', hasEReceipt: false, merchant: '(none)', @@ -1377,6 +1398,7 @@ const transactionReportGroupListItems = [ shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, keyForList: '4', isAmountColumnWide: false, isTaxAmountColumnWide: false, @@ -1399,6 +1421,7 @@ const transactionReportGroupListItems = [ allActions: ['view'], chatReportID: '1706144653204915', created: '2024-12-21 13:05:20', + exported: '', currency: 'USD', formattedFrom: 'Admin', formattedStatus: 'Draft', @@ -1422,6 +1445,7 @@ const transactionReportGroupListItems = [ shouldShowYear: true, shouldShowYearSubmitted: true, shouldShowYearApproved: false, + shouldShowYearExported: false, stateNum: 0, statusNum: 0, to: emptyPersonalDetails, @@ -1873,7 +1897,7 @@ describe('SearchUIUtils', () => { expect(distanceTransaction).toBeDefined(); expect(distanceTransaction?.iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.DISTANCE); - const expectedPropertyCount = 52; + const expectedPropertyCount = 54; expect(Object.keys(distanceTransaction ?? {}).length).toBe(expectedPropertyCount); }); @@ -1906,7 +1930,7 @@ describe('SearchUIUtils', () => { expect(distanceTransaction).toBeDefined(); expect(distanceTransaction?.iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.DISTANCE); - const expectedPropertyCount = 49; + const expectedPropertyCount = 51; expect(Object.keys(distanceTransaction ?? {}).length).toBe(expectedPropertyCount); }); @@ -2764,6 +2788,7 @@ describe('SearchUIUtils', () => { [CONST.SEARCH.TABLE_COLUMNS.DATE]: true, [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false, [CONST.SEARCH.TABLE_COLUMNS.APPROVED]: false, + [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: false, [CONST.SEARCH.TABLE_COLUMNS.STATUS]: true, [CONST.SEARCH.TABLE_COLUMNS.TITLE]: true, [CONST.SEARCH.TABLE_COLUMNS.FROM]: true, @@ -2784,6 +2809,7 @@ describe('SearchUIUtils', () => { [CONST.SEARCH.TABLE_COLUMNS.DATE]: true, [CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: false, [CONST.SEARCH.TABLE_COLUMNS.APPROVED]: false, + [CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: false, [CONST.SEARCH.TABLE_COLUMNS.STATUS]: true, [CONST.SEARCH.TABLE_COLUMNS.TITLE]: true, [CONST.SEARCH.TABLE_COLUMNS.FROM]: false, From d949112490ebd9e19357b9ee826215e0de35293c Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 15:35:18 -0700 Subject: [PATCH 16/17] fix ts --- tests/unit/MoneyRequestReportUtilsTest.ts | 2 ++ tests/unit/Search/handleActionButtonPressTest.ts | 6 ++++++ tests/unit/TransactionGroupListItemTest.tsx | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/tests/unit/MoneyRequestReportUtilsTest.ts b/tests/unit/MoneyRequestReportUtilsTest.ts index 230e7cc55997..49ddea2c80e5 100644 --- a/tests/unit/MoneyRequestReportUtilsTest.ts +++ b/tests/unit/MoneyRequestReportUtilsTest.ts @@ -66,6 +66,7 @@ const transactionItemBaseMock: TransactionListItemType = { submitted: '2024-12-21', approved: undefined, posted: undefined, + exported: undefined, currency: 'USD', date: '2024-12-21', formattedFrom: 'Admin', @@ -93,6 +94,7 @@ const transactionItemBaseMock: TransactionListItemType = { shouldShowYearSubmitted: true, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, isAmountColumnWide: false, isTaxAmountColumnWide: false, tag: '', diff --git a/tests/unit/Search/handleActionButtonPressTest.ts b/tests/unit/Search/handleActionButtonPressTest.ts index bfb339a6c348..2c796e2b148e 100644 --- a/tests/unit/Search/handleActionButtonPressTest.ts +++ b/tests/unit/Search/handleActionButtonPressTest.ts @@ -19,6 +19,7 @@ const mockReportItemWithHold = { submitted: '2024-12-04', approved: undefined, posted: undefined, + exported: undefined, currency: 'USD', isOneTransactionReport: false, isPolicyExpenseChat: false, @@ -70,6 +71,7 @@ const mockReportItemWithHold = { shouldShowYearSubmitted: false, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, transactions: [ { report: { @@ -117,6 +119,7 @@ const mockReportItemWithHold = { submitted: '2024-12-04', approved: undefined, posted: undefined, + exported: undefined, policyID: '48D7178DE42EE9F9', reportID: '1350959062018695', reportType: 'expense', @@ -163,6 +166,7 @@ const mockReportItemWithHold = { shouldShowYearSubmitted: false, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, keyForList: '1049531721038862176', isAmountColumnWide: false, isTaxAmountColumnWide: false, @@ -202,6 +206,7 @@ const mockReportItemWithHold = { submitted: '2024-12-04', approved: undefined, posted: undefined, + exported: undefined, currency: 'USD', hasEReceipt: false, merchant: 'Forbes', @@ -235,6 +240,7 @@ const mockReportItemWithHold = { shouldShowYearSubmitted: false, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, keyForList: '5345995386715609966', isAmountColumnWide: false, isTaxAmountColumnWide: false, diff --git a/tests/unit/TransactionGroupListItemTest.tsx b/tests/unit/TransactionGroupListItemTest.tsx index a6d46d6544bc..e8af17b43762 100644 --- a/tests/unit/TransactionGroupListItemTest.tsx +++ b/tests/unit/TransactionGroupListItemTest.tsx @@ -34,6 +34,7 @@ const mockTransaction: TransactionListItemType = { submitted: '2025-09-19', approved: undefined, posted: undefined, + exported: undefined, currency: 'USD', policy: { id: '06F34677820A4D07', @@ -71,6 +72,7 @@ const mockTransaction: TransactionListItemType = { shouldShowYearSubmitted: false, shouldShowYearApproved: false, shouldShowYearPosted: false, + shouldShowYearExported: false, keyForList: '1', isAmountColumnWide: false, isTaxAmountColumnWide: false, @@ -97,6 +99,7 @@ const mockReport: TransactionReportGroupListItemType = { created: '2025-09-19 20:00:47', submitted: '2025-09-19', approved: undefined, + exported: undefined, currency: 'USD', isOneTransactionReport: true, isOwnPolicyExpenseChat: false, @@ -128,6 +131,7 @@ const mockReport: TransactionReportGroupListItemType = { shouldShowYear: false, shouldShowYearSubmitted: false, shouldShowYearApproved: false, + shouldShowYearExported: false, action: 'view', transactions: [], groupedBy: 'expense-report', From 45f085bdd3715a3ff02fd3fa02761fb154d2d42a Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 17 Dec 2025 15:57:27 -0700 Subject: [PATCH 17/17] improve algo efficiency --- src/libs/SearchUIUtils.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 76d336ea0e3d..f8d919dcd6a5 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -882,13 +882,22 @@ function buildLastExportedActionByReportIDMap(data: OnyxTypes.SearchResults['dat if (isReportActionEntry(key)) { const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ''); const actions = data[key]; - const exportedAction = Object.values(actions) - .filter( - (action): action is OnyxTypes.ReportAction => - action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, - ) - .sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) - .at(0); + const exportedActions = Object.values(actions).filter( + (action): action is OnyxTypes.ReportAction => + action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, + ); + + let exportedAction: OnyxTypes.ReportAction | undefined; + let latestTime = -Infinity; + + for (const action of exportedActions) { + const currentTime = new Date(action.created).getTime(); + if (currentTime > latestTime) { + latestTime = currentTime; + exportedAction = action; + } + } + if (exportedAction) { lastExportedActionByReportID.set(reportID, exportedAction); }