You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-07-13] [$250] Add First Approver and First Approved date columns #90507
Add First Approver and First Approved date columns to the reports columns to match what's available in the detailed export template and close the gap between in-product column configuration and exports.
This is part of a larger effort to add custom columns:
References to the template below are included here.
Users can display the First Approver column (person who approved the report): ${report.approvers[0].email}
Users can display the First Approved column (timestamp of first approval): ${report.approvers[0].date?date("yyyy-MM-dd")?string("yyyy-MM-dd")}
Context
Currently, ~29 columns are available in-product; the detailed export template has 67. Top customers have requested the ability to configure columns in the product that match their export templates to reduce downstream Excel manipulation.
Reference: Web-Expensify/lib/Integrations/defaultExportTemplates/detailed_export.ftl (expensify_detailed_export.txt) for context on available columns.
Performance Considerations
Verify no performance impact on accounts with large report histories
Monitor render times when displaying these columns alongside other data
Testing
Verify columns display correctly with various data states
Verify columns are configurable via the column selection menu
Test on accounts with large report histories for performance impact
🚨 Edited by proposal-police: This proposal was edited at 2026-05-15 10:26:58 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
The Reports table column picker does not offer the report-level First Approver or First Approved date columns, even though those fields are available in the detailed export template. Users can export those values, but cannot configure the in-product Reports table to show the same report approval metadata.
What is the root cause of that problem?
The Reports table column pipeline only models the existing approved date column. CONST.SEARCH.TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT exposes APPROVED, but there are no FIRST_APPROVER / FIRST_APPROVED table column IDs in the column constants or the expense-report allowlist:
The report-row normalizer already receives filtered report actions, and that filtered set includes APPROVED actions, but getReportSections() only formats requester/manager/status/exported fields into the row model. It never extracts the first APPROVED action's created timestamp or actorAccountID, so the row renderer has no value to display for the requested columns:
Finally, the Reports header and wide row renderer only know how to display the existing APPROVED date from item.approved, so a newly selected first-approval column would currently have no header/cell implementation:
What changes do you think we should make in order to solve the problem?
Add two report-level table columns, for example FIRST_APPROVER and FIRST_APPROVED, to CONST.SEARCH.TABLE_COLUMNS, TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT, user-friendly values, and the search column translation mapping. Add corresponding labels such as common.firstApprover and common.firstApproved through the normal language files.
In SearchUIUtils.getReportSections(), derive the first approval once per report from the already supplied filtered reportActions array: find the earliest CONST.REPORT.ACTIONS.TYPE.APPROVED action by created timestamp, using reportActionID as the same deterministic tie-breaker used by report-action ordering. Store firstApproved as that action's created value, and store firstApprover / formattedFirstApprover from the action's actorAccountID via the existing merged personal details lookup. Add those fields to ExpenseReportListItemType, but keep both FIRST_APPROVER and FIRST_APPROVED out of expenseReportColumnNamesToSortingProperty and mark both headers as non-sortable. These values are derived client-side from report actions, while clicking a sortable header writes sortBy:<column> into the search query and sends it to the backend Search API; since there is no backend sort support for sortBy:firstApproved or sortBy:firstApprover, exposing them as sortable would risk an unsupported sort path.
In SearchTableHeader.getExpenseReportHeaders() add the two headers with isColumnSortable: false on both first-approval columns. In ExpenseReportListItemRowWide, render FIRST_APPROVED with DateCell and render FIRST_APPROVER with UserInfoCell, matching the existing SUBMITTED, APPROVED, FROM, and TO cell patterns. Add date-width/year handling for FIRST_APPROVED beside the existing approved-date year logic so old approval dates do not truncate.
Scope boundaries:
Files touched (with call sites): src/CONST/index.ts — TABLE_COLUMNS, TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT, user-friendly column values; src/libs/SearchUIUtils.ts — nonSortableColumns, getReportSections(), getSearchColumnTranslationKey(), year/width helpers; src/components/Search/SearchTableHeader.tsx — getExpenseReportHeaders(); src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx — columnComponents; src/components/Search/SearchList/ListItem/types.ts — ExpenseReportListItemType; language files for the two labels.
Platforms: all platforms that use the shared Search Reports table; wide/table layout displays the new cells, narrow layout remains unchanged like the other configurable table-only columns.
Code paths / callers explicitly NOT touched: detailed export generation in Web-Expensify, approval workflow configuration, report-details transaction columns, and backend search/API contracts.
API contracts or backend behavior changes: no; this uses REPORT_ACTIONS data already subscribed by Search and already filtered to include approved actions.
What alternative solutions did you explore? (Optional)
Using report.approved for the new First Approved column would be simpler, but it would only duplicate the existing Approved column and would not identify the approver. The export template's first-approval fields come from the first approver entry, so deriving both values from the first APPROVED report action keeps the in-product column aligned with the report approval event rather than adding another alias for the current approved date.
No human or bot proposals existed at draft time, so there was no duplicate technical claim to address.
changed the title [-]Add First Approver and First Approved date columns[/-][+][$250] Add First Approver and First Approved date columns[/+]on May 13, 2026
Please re-state the problem that we are trying to solve in this issue.
Currently, the in-product report column configuration menu displays ~29 columns, while the detailed export template includes 67 columns. Users cannot select First Approver (the email of the person who approved the report) or First Approved date (the timestamp of first approval) columns in the Reports view, even though these fields are available in the detailed export template. This forces users to perform downstream Excel manipulation to access these common reporting fields.
What is the current gap in the implementation?
The First Approver and First Approved date columns are missing from the report column configuration system. The CONST.SEARCH.TYPE_CUSTOM_COLUMNS structure in src/CONST/index.ts (lines 7686–7704) defines the canonical list of selectable columns for the EXPENSE_REPORT data type. The EXPENSE_REPORT block registers available columns by referencing TABLE_COLUMNS keys, but lacks entries for the requested fields. To make these columns functional, they must be: (1) registered as new TABLE_COLUMNS keys in src/CONST/index.ts, (2) added to TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT, (3) mapped in getSearchColumnTranslationKey() in src/libs/SearchUIUtils.ts to enable locale lookup, (4) included in getExpenseReportHeaders() in src/components/Search/SearchTableHeader.tsx to render in the table, and (5) given translatable strings in src/languages/en.ts and src/languages/es.ts.
The working pattern is visible in the existing column registrations:
File: src/libs/SearchUIUtils.ts Function: getSearchColumnTranslationKey Description: Add cases for the new columns that return the corresponding translation keys:
File: src/components/Search/SearchTableHeader.tsx Function: getExpenseReportHeaders Description: Add entries in the headers array for the new columns. Both columns source their data from report.approvers, which is an array that may be empty or contain entries with missing fields. The rendering logic must guard against this:
{key: CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVER,// report.approvers may be empty; only render if the first entry exists and has an emailrender: (report)=>{constfirstApprover=report.approvers?.[0];returnfirstApprover?.email??null;},},{key: CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED_DATE,// report.approvers may be empty; only render if the first entry exists and has a daterender: (report)=>{constfirstApprover=report.approvers?.[0];returnfirstApprover?.date??null;},},
The columns intentionally surface only approvers[0] — the first approver — to match the detailed export template field ${report.approvers[0].email}. Reports with multiple approvers retain all approver data in the underlying structure; only the first approver is displayed in these columns, consistent with the export template behavior. Returning null when the array is empty or the field is absent keeps the cell blank rather than crashing.
Platform rendering: SearchTableHeader.tsx is shared across web and native (iOS/Android). No platform-specific branching is required for the column definitions themselves; the existing header rendering infrastructure already handles responsive column visibility. Verify that the column is included in the shouldShowColumn guard (if one exists in this file) for the native narrowed layout, so the columns are hidden on small screens just as other optional columns are.
File: src/languages/en.ts Description: Add translation strings under the appropriate section:
File: src/languages/es.ts Description: Add Spanish translations for the same keys (Spanish locale strings are required for all user-visible text):
firstApprover: 'Primer aprobador',firstApprovedDate: 'Fecha de primera aprobación',
Performance note: These columns read report.approvers[0] — a field already present on the report object fetched for the Search view. No additional network requests or Onyx subscriptions are introduced. For accounts with large report histories the column data is loaded as part of the existing report list query; no extra caching layer is needed. If profiling reveals render-time regressions when both columns are enabled simultaneously, the render callbacks above can be memoized at the call site, but no pre-emptive memoization is added here.
I agree that we expanded the scope of the original issue to make the styling consistent. If a bonus bounty is issued, I think $125 makes sense, bringing the total to $375.
Activity
melvin-bot commentedon May 13, 2026
Triggered auto assignment to Design team member for new feature review - @dubielzyk-expensify (
NewFeature)twisterdotcom commentedon May 13, 2026
I don't think we need a design review here.
KJ21-ENG commentedon May 13, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-05-15 10:26:58 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
The Reports table column picker does not offer the report-level First Approver or First Approved date columns, even though those fields are available in the detailed export template. Users can export those values, but cannot configure the in-product Reports table to show the same report approval metadata.
What is the root cause of that problem?
The Reports table column pipeline only models the existing
approveddate column.CONST.SEARCH.TYPE_CUSTOM_COLUMNS.EXPENSE_REPORTexposesAPPROVED, but there are noFIRST_APPROVER/FIRST_APPROVEDtable column IDs in the column constants or the expense-report allowlist:App/src/CONST/index.ts
Lines 7686 to 7704 in 9d65949
The report-row normalizer already receives filtered report actions, and that filtered set includes
APPROVEDactions, butgetReportSections()only formats requester/manager/status/exported fields into the row model. It never extracts the firstAPPROVEDaction'screatedtimestamp oractorAccountID, so the row renderer has no value to display for the requested columns:App/src/components/Search/index.tsx
Lines 360 to 362 in 9d65949
App/src/libs/ReportUtils.ts
Lines 12957 to 12965 in 9d65949
App/src/libs/SearchUIUtils.ts
Lines 2670 to 2752 in 9d65949
Finally, the Reports header and wide row renderer only know how to display the existing
APPROVEDdate fromitem.approved, so a newly selected first-approval column would currently have no header/cell implementation:App/src/components/Search/SearchTableHeader.tsx
Lines 199 to 217 in 9d65949
App/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx
Lines 76 to 83 in 9d65949
What changes do you think we should make in order to solve the problem?
Add two report-level table columns, for example
FIRST_APPROVERandFIRST_APPROVED, toCONST.SEARCH.TABLE_COLUMNS,TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT, user-friendly values, and the search column translation mapping. Add corresponding labels such ascommon.firstApproverandcommon.firstApprovedthrough the normal language files.In
SearchUIUtils.getReportSections(), derive the first approval once per report from the already supplied filteredreportActionsarray: find the earliestCONST.REPORT.ACTIONS.TYPE.APPROVEDaction bycreatedtimestamp, usingreportActionIDas the same deterministic tie-breaker used by report-action ordering. StorefirstApprovedas that action'screatedvalue, and storefirstApprover/formattedFirstApproverfrom the action'sactorAccountIDvia the existing merged personal details lookup. Add those fields toExpenseReportListItemType, but keep bothFIRST_APPROVERandFIRST_APPROVEDout ofexpenseReportColumnNamesToSortingPropertyand mark both headers as non-sortable. These values are derived client-side from report actions, while clicking a sortable header writessortBy:<column>into the search query and sends it to the backend Search API; since there is no backend sort support forsortBy:firstApprovedorsortBy:firstApprover, exposing them as sortable would risk an unsupported sort path.In
SearchTableHeader.getExpenseReportHeaders()add the two headers withisColumnSortable: falseon both first-approval columns. InExpenseReportListItemRowWide, renderFIRST_APPROVEDwithDateCelland renderFIRST_APPROVERwithUserInfoCell, matching the existingSUBMITTED,APPROVED,FROM, andTOcell patterns. Add date-width/year handling forFIRST_APPROVEDbeside the existing approved-date year logic so old approval dates do not truncate.Scope boundaries:
src/CONST/index.ts—TABLE_COLUMNS,TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT, user-friendly column values;src/libs/SearchUIUtils.ts—nonSortableColumns,getReportSections(),getSearchColumnTranslationKey(), year/width helpers;src/components/Search/SearchTableHeader.tsx—getExpenseReportHeaders();src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx—columnComponents;src/components/Search/SearchList/ListItem/types.ts—ExpenseReportListItemType; language files for the two labels.REPORT_ACTIONSdata already subscribed by Search and already filtered to include approved actions.What alternative solutions did you explore? (Optional)
Using
report.approvedfor the new First Approved column would be simpler, but it would only duplicate the existing Approved column and would not identify the approver. The export template's first-approval fields come from the first approver entry, so deriving both values from the firstAPPROVEDreport action keeps the in-product column aligned with the report approval event rather than adding another alias for the current approved date.No human or bot proposals existed at draft time, so there was no duplicate technical claim to address.
melvin-bot commentedon May 13, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @DylanDylann (
External)[-]Add First Approver and First Approved date columns[/-][+][$250] Add First Approver and First Approved date columns[/+]melvin-bot commentedon May 13, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022054555736846849292
aswin-s commentedon May 13, 2026
Please re-state the problem that we are trying to solve in this issue.
Currently, the in-product report column configuration menu displays ~29 columns, while the detailed export template includes 67 columns. Users cannot select First Approver (the email of the person who approved the report) or First Approved date (the timestamp of first approval) columns in the Reports view, even though these fields are available in the detailed export template. This forces users to perform downstream Excel manipulation to access these common reporting fields.
What is the current gap in the implementation?
The First Approver and First Approved date columns are missing from the report column configuration system. The CONST.SEARCH.TYPE_CUSTOM_COLUMNS structure in src/CONST/index.ts (lines 7686–7704) defines the canonical list of selectable columns for the EXPENSE_REPORT data type. The EXPENSE_REPORT block registers available columns by referencing TABLE_COLUMNS keys, but lacks entries for the requested fields. To make these columns functional, they must be: (1) registered as new TABLE_COLUMNS keys in src/CONST/index.ts, (2) added to TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT, (3) mapped in getSearchColumnTranslationKey() in src/libs/SearchUIUtils.ts to enable locale lookup, (4) included in getExpenseReportHeaders() in src/components/Search/SearchTableHeader.tsx to render in the table, and (5) given translatable strings in src/languages/en.ts and src/languages/es.ts.
The working pattern is visible in the existing column registrations:
App/src/CONST/index.ts
Lines 7686 to 7704 in 9d65949
What changes do you think we should make in order to implement this feature?
File: src/CONST/index.ts
Property: TABLE_COLUMNS
Description: Add two new column key definitions alongside existing column definitions:
File: src/CONST/index.ts
Property: TYPE_CUSTOM_COLUMNS.EXPENSE_REPORT
Description: Register the new columns in the EXPENSE_REPORT block:
File: src/libs/SearchUIUtils.ts
Function: getSearchColumnTranslationKey
Description: Add cases for the new columns that return the corresponding translation keys:
File: src/components/Search/SearchTableHeader.tsx
Function: getExpenseReportHeaders
Description: Add entries in the headers array for the new columns. Both columns source their data from
report.approvers, which is an array that may be empty or contain entries with missing fields. The rendering logic must guard against this:The columns intentionally surface only
approvers[0]— the first approver — to match the detailed export template field${report.approvers[0].email}. Reports with multiple approvers retain all approver data in the underlying structure; only the first approver is displayed in these columns, consistent with the export template behavior. Returningnullwhen the array is empty or the field is absent keeps the cell blank rather than crashing.Platform rendering: SearchTableHeader.tsx is shared across web and native (iOS/Android). No platform-specific branching is required for the column definitions themselves; the existing header rendering infrastructure already handles responsive column visibility. Verify that the column is included in the
shouldShowColumnguard (if one exists in this file) for the native narrowed layout, so the columns are hidden on small screens just as other optional columns are.File: src/languages/en.ts
Description: Add translation strings under the appropriate section:
File: src/languages/es.ts
Description: Add Spanish translations for the same keys (Spanish locale strings are required for all user-visible text):
Performance note: These columns read
report.approvers[0]— a field already present on the report object fetched for the Search view. No additional network requests or Onyx subscriptions are introduced. For accounts with large report histories the column data is loaded as part of the existing report list query; no extra caching layer is needed. If profiling reveals render-time regressions when both columns are enabled simultaneously, therendercallbacks above can be memoized at the call site, but no pre-emptive memoization is added here.What alternative approaches did you explore?
N/A
83 remaining items
melvin-bot commentedon Jul 12, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Jul 12, 2026
Payment Summary
Upwork Job
BugZero Checklist (@mallenexpensify)
KJ21-ENG commentedon Jul 12, 2026
Seems like payment summary has switched roles (Incorrectly) 👀😅
cc: @mallenexpensify
mallenexpensify commentedon Jul 13, 2026
Payment Summary
Contributor: @mukhrr due $375 via NewDot
Contributor+: @DylanDylann due $375 via NewDot
^ Test case created. Thx
@KJ21-ENG melv often does mixed up, I think it'll be fixed soon. Thx
KJ21-ENG commentedon Jul 13, 2026
NW! 👍🏻
mukhrr commentedon Jul 14, 2026
@mallenexpensify thanks! what about this request on price increase here? #90507 (comment) there was an improvement besides orignal bug fix. cc @twisterdotcom @grgia
DylanDylann commentedon Jul 14, 2026
I agree that we expanded the scope of the original issue to make the styling consistent. If a bonus bounty is issued, I think $125 makes sense, bringing the total to $375.
twisterdotcom commentedon Jul 14, 2026
$375 seems adequate to me. Have you both already requested this? Or can I update the payment summary and you update your requested amounts?
mukhrr commentedon Jul 14, 2026
THanks. I requested with $375 in ND
twisterdotcom commentedon Jul 14, 2026
I updated the Payment Summary above.
quinthar commentedon Jul 31, 2026
Approved $375 to @DylanDylann
flaviadefaria commentedon Jul 31, 2026
Approved: @mukhrr paid $375 via NewDot