[HOLD for https://github.com/Expensify/Expensify/issues/664297] Show Scanning in the date column and pin scanning expenses to the top of transaction lists - #93469
Conversation
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
5d97546 to
9a2463b
Compare
|
@DylanDylann Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a2463bddf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
On the narrow layout a transaction's category renders only as the date cell's suffix. The scanning override replaced the whole display string, so a category set on a still-scanning receipt vanished until the scan finished. Apply the override to the date portion only and keep the suffix. Adds a render test for scanning + category on narrow layout.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99802d467f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Scanning transactions stay pinned to the top regardless of the selected sort column or direction | ||
| const scanningComparison = compareScanningPriority(a, b); | ||
| if (scanningComparison !== 0) { | ||
| return scanningComparison; |
There was a problem hiding this comment.
Preserve scan pinning when report rows are grouped
When an expense report is using the default grouped layout (the report layout selection defaults to category unless matrix is stored), this comparator only pins the order of sortedTransactions; the rendered rows are then regrouped via groupTransactionsByCategory/Tag(resolvedTransactions, ...), whose group sorting can move the scan’s group below other groups and puts the empty category/tag group last. A freshly scanned receipt usually has no category/tag, so in the default report transaction table it still appears near the bottom instead of staying pinned to the top. The grouping path needs to keep scanning transactions/groups ahead of the normal group order.
Useful? React with 👍 / 👎.
| missingFieldError={missingFieldError} | ||
| categoryForDisplay={categoryForDisplay} | ||
| createdAt={createdAt} | ||
| createdDateTextOverride={createdDateTextOverride} |
There was a problem hiding this comment.
I suggest passing isScanning to keep things simple
| sortOrder?: SortOrder, | ||
| options?: SortSectionsOptions, | ||
| ) { | ||
| if (!sortBy || !sortOrder) { |
There was a problem hiding this comment.
@wildan-m Should we combine the scanning sort into the current function instead of creating a new function just for scanning sort?
|
@wildan-m The scanning transaction is not at the top when sorting by merchant Screen.Recording.2026-06-15.at.13.43.00.mov |
trjExpensify
left a comment
There was a problem hiding this comment.
Open the expense report that contains the scanning expense. Verify the transaction table also shows "Scanning…" in the Date column and keeps the row at the top in both Date sort directions.
Can you expand on this? In the multi-expense report view, we have the reportLayout group by: Category, Tag, None setting.
If the user is using the Category layout, and the expense scanning has been categorised already, the expense should go in that section. If it hasn't been categorised, it goes in a "Uncategorised" section like it does today. Same logic exists for Tag.
If the user is using the None layout, then I agree.. "Scanning..." expenses will just sit at the top of the table until done, and then sorted however they are sorting the table (the default being by date).
…ch sort - Grouped report layout: float the group containing an in-progress scan to the top. A fresh scan has no category/tag, so its group otherwise sorted to the bottom of the grouped report (and stayed there under any column sort). - Pass an isScanning boolean to DateCell, which renders the scanning label, instead of threading a precomputed override string through the row variants. - Combine the scanning pin into getSortedTransactionData as the primary comparator, removing the separate column-sort helper. - Add a unit test asserting the scanning group is pinned to the top.
…lated to this PR)
|
@wildan-m Is this ready? |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6b8f92eff
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }); | ||
| }; | ||
|
|
||
| return data.sort((a, b) => compareScanningPriority(a, b) || compareColumn(a, b)); |
There was a problem hiding this comment.
Apply scanning priority to custom report-column sorts
When the user sorts the Expenses search by submitterUserID, submitterPayrollID, or orderDealNumbers, getSortedTransactionData returns from the custom-column branch above before reaching this comparator, so scanning transactions are sorted only by that report field instead of being pinned to the top. That leaves scans buried for those supported sortable columns, contrary to the new “regardless of selected column” behavior.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in cce3777 🙏 Those columns (submitterUserID/submitterPayrollID/orderDealNumbers) bypassed the shared compareScanningPriority(a, b) || compareColumn(a, b) comparator via an early return data.sort(...). Moved them into compareColumn so scanning is pinned for every column, and extended the "pin regardless of column" test to cover all three.
@wildan-m I think in this case, we should always pin the scanning expense to the top of the Uncategorized section? cc @trjExpensify Could you confirm again this? |
The submitter/order-number custom columns returned from an early branch in getSortedTransactionData that skipped compareScanningPriority, so scans were not pinned when sorting by those columns (flagged by Codex). Move that branch into compareColumn so the shared comparator pins scanning for every column.
|
@trjExpensify Kindly bump ☝️ |
|
Correct, if they didn't categorize it, we put the scanning expense atop the uncategorized section when using the category layout. 👍 |
…ng-date-sort-top # Conflicts: # src/components/Search/SearchList/ListItem/DateCell.tsx # src/components/TransactionItemRow/TransactionItemRowNarrow.tsx # tests/unit/Search/SearchUIUtilsTest.ts
ee045bc to
7a8a55a
Compare
Per trj's confirmation, grouped layouts (Category/Tag) now pin the scanning expense to the top of its section (e.g. atop the Uncategorized section), matching Expensify Classic. The grouping consumes the scanning-pinned resolvedTransactions list again instead of the column-only sort.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e62d4fb2c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }); | ||
| }; | ||
|
|
||
| return data.sort((a, b) => compareScanningPriority(a, b) || compareColumn(a, b)); |
There was a problem hiding this comment.
Pin scanning rows inside grouped search results
When the Expenses search is grouped, this new comparator is never applied to the expanded child rows: getSortedSections takes the groupBy branch and sorts only the group headers, while GroupChildrenContent rebuilds the children with getSections and does not call getSortedTransactionData. In a grouped Expenses view (e.g. grouped by category/tag), an in-progress scan with today's placeholder date or amount 0 still follows the backend column sort inside its group, so it can remain below other rows even though ungrouped lists are pinned here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0ca256d — added sortTransactionsScanningFirst at the three sites that build grouped Search children (they never reached getSortedTransactionData), plus a unit test.
There was a problem hiding this comment.
This is the paginated / server-sorted case discussed here: #93469 (comment)
Short version: the client-side pin can only reorder rows already loaded, so a scan on an unfetched page has nothing to pin. Making it work across pages is a bigger Search-data change that may be outside this PR's scope.
Grouped Search children are rebuilt from a per-group snapshot via getSections and never pass through getSortedTransactionData, so an in-progress scan followed the backend column sort inside its group instead of being pinned (flagged by Codex). Add sortTransactionsScanningFirst and apply it wherever those children are built, so a scanning expense sits atop its section — matching trj's confirmed behavior and the ungrouped list and report layouts.
|
Thanks for confirming, @trjExpensify 🙏 — @DylanDylann was right. Grouped layouts now pin the scanning expense atop its section: report view in |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9253974026
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }); | ||
| }; | ||
|
|
||
| return data.sort((a, b) => compareScanningPriority(a, b) || compareColumn(a, b)); |
There was a problem hiding this comment.
Keep acknowledged scans in the sorted result set
When a receipt is still in SCANNING but the optimistic ADD has already been acknowledged, changing to a server-sorted column such as Amount descending on a result set with more than one page can make the backend return a page that does not contain the zero-amount scan. This comparator only pins items already present in the paginated snapshot, so the scanning expense is not pinned to the top and may disappear until more results are fetched. Please merge active scanning transactions into the search data (as the optimistic path does) or otherwise ensure the page being sorted contains them before applying this priority.
Useful? React with 👍 / 👎.
| { | ||
| text: translate('common.unreported'), | ||
| value: CONST.SEARCH.STATUS.EXPENSE.UNREPORTED, | ||
| }, | ||
| { | ||
| text: translate('common.draft'), | ||
| value: CONST.SEARCH.STATUS.EXPENSE.DRAFTS, | ||
| }, | ||
| { | ||
| text: translate('common.outstanding'), | ||
| value: CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING, | ||
| }, | ||
| { | ||
| text: translate('iou.approved'), | ||
| value: CONST.SEARCH.STATUS.EXPENSE.APPROVED, | ||
| }, | ||
| { | ||
| text: translate('iou.settledExpensify'), | ||
| value: CONST.SEARCH.STATUS.EXPENSE.PAID, | ||
| }, |
There was a problem hiding this comment.
How did this change end up in this PR? I would suggest reverting this change to keep PR clean and avoid unrelated updates
|
@roryabraham I haven't finished reviewing this PR yet. I approved it by mistake. Sorry for the wrong bump |
|
@wildan-m When I try sorting multiple times, the scanning expense sometimes isn't pinned to the top Screen.Recording.2026-07-13.at.14.53.42.mov |
The branch had drifted from main's refactored getSortedTransactionData and accumulated unrelated object-literal reformatting, bloating the SearchUIUtils diff to ~600 lines. Reset the file to main and re-apply scanning minimally: getSortedTransactionData now column-sorts (getColumnSortedTransactionData) then pins scans via sortTransactionsScanningFirst. This pins every column uniformly (subsuming the earlier submitter-column fix) and shrinks the diff to ~30 lines.
|
Thanks for catching this, @DylanDylann. It's the same cause Codex flagged at The pin runs on the client, so it can only reorder the rows already loaded on screen. When you sort by a column the server sorts (like Amount) and there's more than one page of results, the server returns a page ordered by that column — and a scan (amount 0, no date yet) can land on a page that hasn't loaded. Since it's not on screen, there's nothing to pin, so it isn't pinned and looks like it disappears until more rows load. That's why it's hit-or-miss — it depends on which page the scan falls on. When the results fit on one page (the usual case), it pins correctly every time. Making it work across pages would be a bigger change to how Search loads data — pulling active scans in from the local data and merging them into the results before sorting, while keeping totals, counts, and "load more" correct. That feels outside the original scope here (show the Scanning date and pin scans in the list), but let me know if you think it should be handled as part of this PR. |
|
I think it's worth fixing, because we need to keep scanning expenses at the top of the list, even when users sort by any field. So the scanning transaction should be prioritized to appear on the first page, regardless of the sorting. @roryabraham I believe we need to make some adjustments on the backend side to achieve this expected behavior |
|
@roryabraham Kindly bump ☝️ |
|
@MelvinBot create a new GitHub issue to ensure that Scanning expenses always appear at the front of search results on the expense search page. Create it in the Expensify/Expensify repo, assign me, and label it Daily. Put this PR on HOLD for the issue you created. |
|
🤖 Done,
This PR pins scanning expenses to the top of the Expenses tab and report transaction tables; the new issue tracks extending that same treatment to the expense Search results page. I didn't find an existing duplicate for the Search-page ordering. |
|
Quick update: I have a draft PR for the backend change. But I still need to test end-to-end |
Explanation of Change
A freshly scanned receipt is created with a placeholder date of today at midnight, so the expense list shows a real-looking date for a receipt whose date isn't actually known yet — and under the default newest-first sort, the in-progress scan sinks below every other expense created that day, making it hard to track the scan's progress.
While the receipt is scanning, the date cell now shows "Scanning…" — the same treatment and copy the merchant cell already uses — and the row is pinned to the top of both the Expenses tab and the report's transaction table, regardless of the selected sort column or direction. Both changes key off the existing scanning state, so once the scan completes the row automatically reverts to its real date and chronological position with no extra plumbing. The pin takes precedence over the existing prioritization of rows that need attention, and only applies on the views that show the scanning state.
Fixed Issues
$ #89966
PROPOSAL: #89966 (comment)
Tests
Offline tests
QA Steps
Same as tests.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Kapture.2026-06-13.at.22.09.41.mp4
Android: mWeb Chrome
Kapture.2026-06-13.at.22.13.41.mp4
iOS: Native
Kapture.2026-06-13.at.21.48.29.mp4
iOS: mWeb Safari
Kapture.2026-06-13.at.22.00.44.mp4
MacOS: Chrome / Safari
Kapture.2026-06-13.at.21.09.07.mp4