Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4a0e174
added missing translations
mukhrr Jun 10, 2026
0fc96ed
added tests
mukhrr Jun 10, 2026
647ad02
added tests
mukhrr Jun 10, 2026
eea14df
hide empty columns from table
mukhrr Jun 10, 2026
66750bf
scoped first-approver detection to the current approval cycle in bot…
mukhrr Jun 15, 2026
4d6bec6
removed FIRST_APPROVED sortable feature
mukhrr Jun 15, 2026
b668b44
Only the avatar was used from that object (accountID and display nam…
mukhrr Jun 15, 2026
e9c62bd
DRY shouldShowYear for approve related columns
mukhrr Jun 15, 2026
c2551b1
firstApproverDetails now defaults to undefined.
mukhrr Jun 15, 2026
8e7f58f
Merge origin/main into fix/90507
mukhrr Jun 15, 2026
83de396
fixed prettier
mukhrr Jun 15, 2026
aafd782
removed unnecessary shouldShowYearFirstApproved
mukhrr Jun 15, 2026
6235691
removed shouldShowYearFirstApproved. made first approved have the sta…
mukhrr Jun 15, 2026
f6c6629
added first approver and first approved to sorting list of sorting cols
mukhrr Jun 15, 2026
f605ea3
Merge origin/main into fix/90507
mukhrr Jun 21, 2026
b1789f1
Merge origin/main into fix/90507
mukhrr Jun 24, 2026
973f9f7
Merge remote-tracking branch 'upstream/main' into fix/90507
mukhrr Jun 25, 2026
5c494a6
fixed tests
mukhrr Jun 25, 2026
654eeba
lowercase new sort keys
mukhrr Jun 26, 2026
606bbdd
this removes the multi-cycle guard
mukhrr Jun 26, 2026
6dbebe9
Merge remote-tracking branch 'upstream/main' into fix/90507
mukhrr Jun 30, 2026
b1fedf2
Merge remote-tracking branch 'upstream/main' into fix/90507
mukhrr Jul 1, 2026
2bbb390
Merge upstream/main into fix/90507
mukhrr Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6600,6 +6600,8 @@ const CONST = {
DATE: this.TABLE_COLUMNS.DATE,
SUBMITTED: this.TABLE_COLUMNS.SUBMITTED,
APPROVED: this.TABLE_COLUMNS.APPROVED,
FIRST_APPROVER: this.TABLE_COLUMNS.FIRST_APPROVER,
Comment thread
mukhrr marked this conversation as resolved.
FIRST_APPROVED: this.TABLE_COLUMNS.FIRST_APPROVED,
EXPORTED: this.TABLE_COLUMNS.EXPORTED,
STATUS: this.TABLE_COLUMNS.STATUS,
TITLE: this.TABLE_COLUMNS.TITLE,
Expand Down Expand Up @@ -6792,6 +6794,8 @@ const CONST = {
DATE: 'date',
SUBMITTED: 'submitted',
APPROVED: 'approved',
FIRST_APPROVER: 'firstapprover',
FIRST_APPROVED: 'firstapproved',
POSTED: 'posted',
EXPORTED: 'exported',
MERCHANT: 'merchant',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@ function ExpenseReportListItemRowWide({
/>
</View>
),
[CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVER]: (
<View style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVER)]}>
{!!item.firstApproverAccountID && (
<UserInfoCell
accountID={item.firstApproverAccountID}
avatar={item.firstApproverAvatar}
displayName={item.formattedFirstApprover ?? ''}
isLargeScreenWidth
/>
)}
</View>
),
[CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED]: (
<View style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED)]}>
<DateCell
date={item.firstApproved ?? ''}
showTooltip
isLargeScreenWidth
/>
</View>
),
[CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: (
<View style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.EXPORTED, {isExportedColumnWide: item.shouldShowYearExported})]}>
<DateCell
Expand Down
13 changes: 13 additions & 0 deletions src/components/Search/SearchList/ListItem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {ListItem} from '@components/SelectionList/types';

import type {TransactionPreviewData} from '@libs/actions/Search';
import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab';
import type {AvatarSource} from '@libs/UserAvatarUtils';

import type CONST from '@src/CONST';
import type {
Expand Down Expand Up @@ -242,6 +243,18 @@ type TransactionReportGroupListItemType = TransactionGroupListItemType & {groupe
/** The date the report was exported */
exported?: string;

/** The date of the report's first approval (created date of the earliest APPROVED/FORWARDED report action) */
firstApproved?: string;

/** The avatar of the first approver */
firstApproverAvatar?: AvatarSource;

/** Account ID of the first approver (actor on the earliest APPROVED/FORWARDED report action) */
firstApproverAccountID?: number;

/** Final and formatted "first approver" value used for displaying and sorting */
formattedFirstApprover?: string;

/** Whether the status field should be shown in a pending state */
shouldShowStatusAsPending?: boolean;

Expand Down
8 changes: 8 additions & 0 deletions src/components/Search/SearchTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ const getExpenseReportHeaders = (profileIcon?: IconAsset): SearchColumnConfig[]
columnName: CONST.SEARCH.TABLE_COLUMNS.APPROVED,
translationKey: 'search.filters.approved',
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVER,
translationKey: 'search.filters.firstApprover',
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED,
translationKey: 'search.filters.firstApproved',
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.EXPORTED,
translationKey: 'search.filters.exported',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8684,6 +8684,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
past: 'Vergangenes',
submitted: 'Eingereicht',
approved: 'Genehmigt',
firstApprover: 'Erste:r Genehmiger:in',
firstApproved: 'Zuerst genehmigt',
paid: 'Bezahlt',
exported: 'Exportiert',
posted: 'Gebucht',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8779,6 +8779,8 @@ const translations = {
past: 'Past',
submitted: 'Submitted',
approved: 'Approved',
firstApprover: 'First approver',
firstApproved: 'First approved',
paid: 'Paid',
exported: 'Exported',
posted: 'Posted',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8493,6 +8493,8 @@ ${amount} para ${merchant} - ${date}`,
past: 'Anterior',
submitted: 'Envío',
approved: 'Aprobación',
firstApprover: 'Primer aprobador',
firstApproved: 'Primera aprobación',
paid: 'Pago',
exported: 'Exportación',
posted: 'Contabilización',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8718,6 +8718,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
past: 'Passé',
submitted: 'Soumis',
approved: 'Approuvé',
firstApprover: 'Premier approbateur',
firstApproved: 'Approuvé en premier',
paid: 'Payé',
exported: 'Exporté',
posted: 'Publié',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8674,6 +8674,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
past: 'Passato',
submitted: 'Inviato',
approved: 'Approvato',
firstApprover: 'Primo approvatore',
firstApproved: 'Approvato per primo',
paid: 'Pagato',
exported: 'Esportato',
posted: 'Pubblicato',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8559,6 +8559,8 @@ ${reportName}`,
past: '過去',
submitted: '送信済み',
approved: '承認済み',
firstApprover: '最初の承認者',
firstApproved: '最初に承認済み',
paid: '支払い済み',
exported: 'エクスポート済み',
posted: '投稿日',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8643,6 +8643,8 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
past: 'Verleden',
submitted: 'Ingediend',
approved: 'Goedgekeurd',
firstApprover: 'Eerste gemachtigde',
firstApproved: 'Eerst goedgekeurd',
paid: 'Betaald',
exported: 'Geëxporteerd',
posted: 'Gepost',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8625,6 +8625,8 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
past: 'Przeszłe',
submitted: 'Przesłano',
approved: 'Zatwierdzono',
firstApprover: 'Pierwszy zatwierdzający',
firstApproved: 'Najpierw zatwierdzono',
paid: 'Zapłacono',
exported: 'Wyeksportowano',
posted: 'Opublikowano',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8631,6 +8631,8 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
past: 'Passado',
submitted: 'Enviado',
approved: 'Aprovado',
firstApprover: 'Primeira pessoa aprovadora',
firstApproved: 'Primeira aprovada',
paid: 'Pago',
exported: 'Exportado',
posted: 'Publicado',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8381,6 +8381,8 @@ ${reportName}`,
past: '过去',
submitted: '已提交',
approved: '已批准',
firstApprover: '首位审批人',
firstApproved: '首次批准',
paid: '已支付',
exported: '已导出',
posted: '已发布',
Expand Down
36 changes: 36 additions & 0 deletions src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ const expenseReportColumnNamesToSortingProperty: ExpenseReportSorting = {
[CONST.SEARCH.TABLE_COLUMNS.DATE]: 'created' as const,
[CONST.SEARCH.TABLE_COLUMNS.SUBMITTED]: 'submitted' as const,
[CONST.SEARCH.TABLE_COLUMNS.APPROVED]: 'approved' as const,
[CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVER]: 'formattedFirstApprover' as const,
[CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED]: 'firstApproved' as const,
Comment thread
grgia marked this conversation as resolved.
[CONST.SEARCH.TABLE_COLUMNS.EXPORTED]: 'exported' as const,
[CONST.SEARCH.TABLE_COLUMNS.STATUS]: 'formattedStatus' as const,
[CONST.SEARCH.TABLE_COLUMNS.TITLE]: 'reportName' as const,
Expand Down Expand Up @@ -1750,6 +1752,7 @@ type PreprocessingContext = {
violations: Record<string, OnyxTypes.TransactionViolation[] | undefined>;
shouldShowMerchant: boolean;
lastExportedActionByReportID: Map<string, OnyxTypes.ReportAction>;
firstApprovedActionByReportID: Map<string, OnyxTypes.ReportAction>;
moneyRequestReportActionsByTransactionID: Map<string, OnyxTypes.ReportAction>;
holdReportActionsByTransactionID: Map<string, OnyxTypes.ReportAction>;
allHoldReportActions: Map<string, OnyxTypes.ReportAction>;
Expand All @@ -1776,6 +1779,7 @@ function createPreprocessingContext(): PreprocessingContext {
violations: {},
shouldShowMerchant: false,
lastExportedActionByReportID: new Map(),
firstApprovedActionByReportID: new Map(),
moneyRequestReportActionsByTransactionID: new Map(),
holdReportActionsByTransactionID: new Map(),
allHoldReportActions: new Map(),
Expand Down Expand Up @@ -1806,6 +1810,10 @@ function processReportActionEntry(ctx: PreprocessingContext, key: string, action
let latestExportTime = -Infinity;
let latestExportAction: OnyxTypes.ReportAction | undefined;

// The first approver is the actor on the earliest APPROVED/FORWARDED action.
let firstApprovalTime = Infinity;
let firstApprovalAction: OnyxTypes.ReportAction | undefined;

for (const action of Object.values(actions)) {
if (action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV || action.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION) {
const currentTime = new Date(action.created).getTime();
Expand All @@ -1815,6 +1823,14 @@ function processReportActionEntry(ctx: PreprocessingContext, key: string, action
}
}

if (action.actionName === CONST.REPORT.ACTIONS.TYPE.APPROVED || action.actionName === CONST.REPORT.ACTIONS.TYPE.FORWARDED) {
const currentTime = new Date(action.created).getTime();
if (currentTime < firstApprovalTime) {
firstApprovalTime = currentTime;
firstApprovalAction = action;
}
}

if (isMoneyRequestAction(action)) {
const originalMessage = getOriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.IOU>(action);
const transactionID = originalMessage?.IOUTransactionID;
Expand All @@ -1833,6 +1849,10 @@ function processReportActionEntry(ctx: PreprocessingContext, key: string, action
if (latestExportAction) {
ctx.lastExportedActionByReportID.set(reportID, latestExportAction);
}

if (firstApprovalAction) {
ctx.firstApprovedActionByReportID.set(reportID, firstApprovalAction);
}
}

/**
Expand Down Expand Up @@ -2790,6 +2810,7 @@ function getReportSections({
violations: allViolations,
shouldShowMerchant,
lastExportedActionByReportID,
firstApprovedActionByReportID,
moneyRequestReportActionsByTransactionID,
holdReportActionsByTransactionID,
transactionsByReportID,
Expand Down Expand Up @@ -2856,8 +2877,15 @@ function getReportSections({
emptyPersonalDetails;
const toDetails = !shouldShowBlankTo && reportItem.managerID ? mergedPersonalDetails?.[reportItem.managerID] : emptyPersonalDetails;

// First approver/approved come from the earliest APPROVED/FORWARDED report action; blank when the report has no approval.
const firstApprovedAction = firstApprovedActionByReportID.get(reportItem.reportID);
const firstApproverAccountID = firstApprovedAction?.actorAccountID;
const firstApproverDetails = firstApproverAccountID ? mergedPersonalDetails?.[firstApproverAccountID] : undefined;
const firstApproved = firstApprovedAction?.created ?? '';

const formattedFrom = formatPhoneNumber(getDisplayNameOrDefault(fromDetails));
const formattedTo = !shouldShowBlankTo ? formatPhoneNumber(getDisplayNameOrDefault(toDetails)) : '';
const formattedFirstApprover = firstApproverAccountID ? formatPhoneNumber(getDisplayNameOrDefault(firstApproverDetails)) : '';

const formattedStatus = getReportStatusTranslation({stateNum: reportItem.stateNum, statusNum: reportItem.statusNum, translate});
const policy = getPolicyFromKey(data, reportItem);
Expand Down Expand Up @@ -2897,6 +2925,10 @@ function getReportSections({
from: (fromDetails ?? emptyPersonalDetails) as OnyxTypes.PersonalDetails,
to: (toDetails ?? emptyPersonalDetails) as OnyxTypes.PersonalDetails,
exported: lastExportedActionByReportID.get(reportItem.reportID)?.created ?? '',
firstApproved,
firstApproverAvatar: firstApproverDetails?.avatar,
firstApproverAccountID,
formattedFirstApprover,
formattedFrom,
formattedTo,
formattedStatus,
Expand Down Expand Up @@ -4319,6 +4351,10 @@ function getSearchColumnTranslationKey(column: SearchSortBy): TranslationPaths {
return 'common.submitted';
case CONST.SEARCH.TABLE_COLUMNS.APPROVED:
return 'search.filters.approved';
case CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVER:
return 'search.filters.firstApprover';
case CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED:
return 'search.filters.firstApproved';
case CONST.SEARCH.TABLE_COLUMNS.POSTED:
return 'search.filters.posted';
case CONST.SEARCH.TABLE_COLUMNS.EXPORTED:
Expand Down
13 changes: 9 additions & 4 deletions src/styles/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1907,16 +1907,20 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
columnWidth = {...getWidthStyle(variables.w130), ...styles.alignItemsCenter};
break;
case CONST.SEARCH.TABLE_COLUMNS.SUBMITTED:
columnWidth = {...getWidthStyle(isSubmittedColumnWide ? variables.w92 : variables.w72)};
columnWidth = {...getWidthStyle(isSubmittedColumnWide ? variables.w102 : variables.w62)};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mukhrr Why do you change this styles?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To fix this truncation
image

We decided to fix all truncated dates here #93169 (comment)
image

break;
case CONST.SEARCH.TABLE_COLUMNS.APPROVED:
columnWidth = {...getWidthStyle(isApprovedColumnWide ? variables.w92 : variables.w72)};
columnWidth = {...getWidthStyle(isApprovedColumnWide ? variables.w102 : variables.w62)};
break;
case CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED:
// Fixed width: wide enough for both the long "First approved" header and a past-year date, so no year-based widening is needed.
columnWidth = {...getWidthStyle(variables.w102)};
Comment on lines +1915 to +1917

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mukhrr Sorry for the back and forth, but I think we should maintain consistency with the other fields to avoid regression and reapply isFirstApprovedColumnWide to align with the other columns. If you have any ideas to optimize this, feel free to share them then we could handle it in a follow-up

@mukhrr mukhrr Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DylanDylann what regressions do you refer for example? We get truncated "First App.." table column header if we do the same with others

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reminder. Everything looks fine, so we can move forward

break;
case CONST.SEARCH.TABLE_COLUMNS.POSTED:
columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w92 : variables.w72)};
columnWidth = {...getWidthStyle(isPostedColumnWide ? variables.w102 : variables.w62)};
break;
case CONST.SEARCH.TABLE_COLUMNS.EXPORTED:
columnWidth = {...getWidthStyle(isExportedColumnWide ? variables.w92 : variables.w72)};
columnWidth = {...getWidthStyle(isExportedColumnWide ? variables.w102 : variables.w62)};
break;
case CONST.SEARCH.TABLE_COLUMNS.DATE:
columnWidth = {...getWidthStyle(isDateColumnWide ? variables.w102 : variables.w62)};
Expand Down Expand Up @@ -1996,6 +2000,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
case CONST.SEARCH.TABLE_COLUMNS.MERCHANT:
case CONST.SEARCH.TABLE_COLUMNS.FROM:
case CONST.SEARCH.TABLE_COLUMNS.TO:
case CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVER:
case CONST.SEARCH.TABLE_COLUMNS.ASSIGNEE:
case CONST.SEARCH.TABLE_COLUMNS.TITLE:
case CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION:
Expand Down
9 changes: 8 additions & 1 deletion tests/unit/ButtonStyleUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import CONST from '@src/CONST';
import type {ThemeStyles} from '@src/styles';
import type {ThemeColors} from '@src/styles/theme/types';
import createStyleUtils from '@src/styles/utils';
import variables from '@src/styles/variables';

const mockTheme = {} as ThemeColors;

Expand All @@ -22,7 +23,7 @@ const mockStyles = {
buttonOpacityDisabled: {opacity: 0.5},
} as unknown as ThemeStyles;

const {getButtonSizeStyle, getButtonPaddingStyle, getButtonStyleWithIcon, getButtonVariantStyles} = createStyleUtils(mockTheme, mockStyles);
const {getButtonSizeStyle, getButtonPaddingStyle, getButtonStyleWithIcon, getButtonVariantStyles, getReportTableColumnStyles} = createStyleUtils(mockTheme, mockStyles);

describe('getButtonSizeStyle', () => {
it.each([
Expand Down Expand Up @@ -88,3 +89,9 @@ describe('getButtonVariantStyles', () => {
});
});
});

describe('getReportTableColumnStyles - First approved column width', () => {
it('uses a fixed wide width (fits the long header and a past-year date, so no year-based widening)', () => {
expect(getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.FIRST_APPROVED)).toEqual({width: variables.w102});
});
});
2 changes: 2 additions & 0 deletions tests/unit/Search/ColumnAvailabilityTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ describe('Column availability single source of truth', () => {
// Expense-report (report list) view.
TABLE_COLUMNS.REIMBURSABLE_TOTAL,
TABLE_COLUMNS.NON_REIMBURSABLE_TOTAL,
TABLE_COLUMNS.FIRST_APPROVER,
TABLE_COLUMNS.FIRST_APPROVED,
// Grouped search views.
TABLE_COLUMNS.EXPENSES,
TABLE_COLUMNS.FEED,
Expand Down
Loading
Loading