From 796e69dfc594aeeae6e8607b614436f514025e2b Mon Sep 17 00:00:00 2001
From: nkdengineer <161821005+nkdengineer@users.noreply.github.com>
Date: Fri, 7 Nov 2025 14:00:02 +0700
Subject: [PATCH 01/20] Revert "[CP Staging] Revert "Remove Review button from
Inbox and Reports page""
---
src/CONST/index.ts | 2 -
src/components/AnimatedCollapsible/index.tsx | 35 +-
src/components/MoneyReportHeader.tsx | 2 +-
.../MoneyRequestReportPreviewContent.tsx | 32 +-
.../MoneyRequestReportPreview/index.tsx | 3 +-
.../MoneyRequestReportPreview/types.ts | 3 +-
.../Search/ActionCell.tsx | 6 +-
.../Search/TransactionGroupListItem.tsx | 30 +-
src/languages/de.ts | 1 +
src/languages/en.ts | 1 +
src/languages/es.ts | 1 +
src/languages/fr.ts | 1 +
src/languages/it.ts | 1 +
src/languages/ja.ts | 1 +
src/languages/nl.ts | 1 +
src/languages/pl.ts | 1 +
src/languages/pt-BR.ts | 1 +
src/languages/zh-hans.ts | 1 +
src/libs/ReportPreviewActionUtils.ts | 98 +-----
src/libs/SearchUIUtils.ts | 44 +--
tests/actions/ReportPreviewActionUtilsTest.ts | 315 +-----------------
tests/ui/MoneyRequestReportPreview.test.tsx | 89 -----
.../MoneyRequestReportButtonUtils.test.ts | 1 -
tests/unit/Search/SearchUIUtilsTest.ts | 80 +----
24 files changed, 120 insertions(+), 630 deletions(-)
diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index 5b64e0a6f311..60a799d99397 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -1147,7 +1147,6 @@ const CONST = {
REPORT_PREVIEW_ACTIONS: {
VIEW: 'view',
ADD_EXPENSE: 'addExpense',
- REVIEW: 'review',
SUBMIT: 'submit',
APPROVE: 'approve',
PAY: 'pay',
@@ -6517,7 +6516,6 @@ const CONST = {
},
ACTION_TYPES: {
VIEW: 'view',
- REVIEW: 'review',
SUBMIT: 'submit',
APPROVE: 'approve',
PAY: 'pay',
diff --git a/src/components/AnimatedCollapsible/index.tsx b/src/components/AnimatedCollapsible/index.tsx
index d2b5930ebf2a..d4a9b9fca09f 100644
--- a/src/components/AnimatedCollapsible/index.tsx
+++ b/src/components/AnimatedCollapsible/index.tsx
@@ -21,6 +21,9 @@ type AnimatedCollapsibleProps = {
/** Header content to display above the collapsible content */
header: ReactNode;
+ /** Description content to display below the header */
+ description?: ReactNode;
+
/** Duration of expansion animation */
duration?: number;
@@ -53,6 +56,7 @@ function AnimatedCollapsible({
isExpanded,
children,
header,
+ description,
duration = 300,
style,
headerStyle,
@@ -66,9 +70,9 @@ function AnimatedCollapsible({
const theme = useTheme();
const styles = useThemeStyles();
const contentHeight = useSharedValue(0);
+ const descriptionHeight = useSharedValue(0);
const hasExpanded = useSharedValue(isExpanded);
const [isRendered, setIsRendered] = React.useState(isExpanded);
-
useEffect(() => {
hasExpanded.set(isExpanded);
if (isExpanded) {
@@ -99,6 +103,21 @@ function AnimatedCollapsible({
return withTiming(hasExpanded.get() ? 1 : 0, {duration, easing});
});
+ const descriptionOpacity = useDerivedValue(() => {
+ return withTiming(!hasExpanded.get() ? 1 : 0, {duration, easing});
+ });
+
+ const descriptionAnimatedHeight = useDerivedValue(() => {
+ return withTiming(!isExpanded ? descriptionHeight.get() : 0, {duration, easing});
+ });
+
+ const descriptionAnimatedStyle = useAnimatedStyle(() => {
+ return {
+ opacity: descriptionOpacity.get(),
+ height: descriptionAnimatedHeight.get(),
+ };
+ }, []);
+
const contentAnimatedStyle = useAnimatedStyle(() => {
return {
height: animatedHeight.get(),
@@ -129,6 +148,20 @@ function AnimatedCollapsible({
)}
+ {!!description && (
+
+ {
+ const height = e.nativeEvent.layout.height;
+ if (height) {
+ descriptionHeight.set(height);
+ }
+ }}
+ >
+ {description}
+
+
+ )}
{isExpanded || isRendered ? (
{
return getReportPreviewAction(
- violations,
isIouReportArchived || isChatReportArchived,
- currentUserDetails.email ?? '',
iouReport,
policy,
transactions,
@@ -491,22 +486,8 @@ function MoneyRequestReportPreviewContent({
isPaidAnimationRunning,
isApprovedAnimationRunning,
isSubmittingAnimationRunning,
- areStrictPolicyRulesEnabled,
);
- }, [
- isPaidAnimationRunning,
- isApprovedAnimationRunning,
- isSubmittingAnimationRunning,
- violations,
- iouReport,
- policy,
- transactions,
- isIouReportArchived,
- invoiceReceiverPolicy,
- isChatReportArchived,
- areStrictPolicyRulesEnabled,
- currentUserDetails.email,
- ]);
+ }, [isPaidAnimationRunning, isApprovedAnimationRunning, isSubmittingAnimationRunning, iouReport, policy, transactions, isIouReportArchived, invoiceReceiverPolicy, isChatReportArchived]);
const addExpenseDropdownOptions = useMemo(
() => getAddExpenseDropdownOptions(iouReport?.reportID, policy, chatReportID, iouReport?.parentReportID, lastDistanceExpenseType),
@@ -583,15 +564,6 @@ function MoneyRequestReportPreviewContent({
}}
/>
) : null,
- [CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW]: (
-
+ )}
+
{isExpanded || isRendered ? (
({
});
const getDescription = useMemo(() => {
- if (!hasViolations) {
+ if (!hasViolations || isSettled(item.reportID)) {
return;
}
return (
@@ -327,7 +328,7 @@ function TransactionGroupListItem({
{translate('reportViolations.reportContainsExpensesWithViolations')}
);
- }, [hasViolations, styles.alignItemsCenter, styles.flexRow, styles.ml3, styles.mr1, styles.mv1, styles.textDanger, styles.textMicro, theme.danger, translate]);
+ }, [hasViolations, item.reportID, styles.alignItemsCenter, styles.flexRow, styles.ml3, styles.mr1, styles.mv1, styles.textDanger, styles.textMicro, theme.danger, translate]);
return (
From 957dc5b21c9248f5a7efdbbb5a3492a9b68d362b Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Mon, 10 Nov 2025 14:52:33 +0700
Subject: [PATCH 04/20] fix submit button in the case held expense
---
src/libs/actions/Search.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 5b2eb1471591..e7ae1675783d 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -84,7 +84,7 @@ function handleActionButtonPress(
const allReportTransactions = (isTransactionGroupListItemType(item) ? item.transactions : [item]) as SearchTransaction[];
const hasHeldExpense = hasHeldExpenses('', allReportTransactions);
- if (hasHeldExpense || isInMobileSelectionMode) {
+ if ((hasHeldExpense && item.action !== CONST.SEARCH.ACTION_TYPES.SUBMIT) || isInMobileSelectionMode) {
goToItem();
return;
}
From 18c261b88a74de58b9560f3a6ac118bb82896856 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Mon, 10 Nov 2025 14:57:45 +0700
Subject: [PATCH 05/20] fix submit button in the case held expense
---
src/libs/TransactionUtils/index.ts | 2 +-
tests/actions/IOUTest.ts | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index da4424cb4280..a09f491ccb68 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -293,7 +293,7 @@ function isPartialTransaction(transaction: OnyxEntry): boolean {
}
function isPendingCardOrScanningTransaction(transaction: OnyxEntry): boolean {
- return (isExpensifyCardTransaction(transaction) && isPending(transaction)) || isPartialTransaction(transaction) || (isScanRequest(transaction) && isScanning(transaction));
+ return (isExpensifyCardTransaction(transaction) && isPending(transaction)) || (isScanRequest(transaction) && isMerchantMissing(transaction) && isAmountMissing(transaction)) || (isScanRequest(transaction) && isScanning(transaction));
}
/**
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index dddce0a74e47..8ec7877f5823 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -6287,7 +6287,7 @@ describe('actions/IOU', () => {
source: 'test',
state: CONST.IOU.RECEIPT_STATE.SCAN_FAILED,
},
- merchant: 'test merchant',
+ merchant: '',
modifiedMerchant: undefined,
};
@@ -6337,7 +6337,7 @@ describe('actions/IOU', () => {
source: 'test',
state: CONST.IOU.RECEIPT_STATE.SCAN_FAILED,
},
- merchant: 'test merchant',
+ merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
modifiedMerchant: undefined,
};
From 8b2b4393563ff5d290cef28f2cdf7b3ff5bd3c6f Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Mon, 10 Nov 2025 14:59:15 +0700
Subject: [PATCH 06/20] run prettier
---
src/libs/TransactionUtils/index.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index a09f491ccb68..25c320891bfa 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -293,7 +293,11 @@ function isPartialTransaction(transaction: OnyxEntry): boolean {
}
function isPendingCardOrScanningTransaction(transaction: OnyxEntry): boolean {
- return (isExpensifyCardTransaction(transaction) && isPending(transaction)) || (isScanRequest(transaction) && isMerchantMissing(transaction) && isAmountMissing(transaction)) || (isScanRequest(transaction) && isScanning(transaction));
+ return (
+ (isExpensifyCardTransaction(transaction) && isPending(transaction)) ||
+ (isScanRequest(transaction) && isMerchantMissing(transaction) && isAmountMissing(transaction)) ||
+ (isScanRequest(transaction) && isScanning(transaction))
+ );
}
/**
From 10dda323fe46dd1a107a92f74c4b87bc279ed8d1 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Thu, 13 Nov 2025 14:25:23 +0700
Subject: [PATCH 07/20] show view button for approve/pay with held expense
---
src/libs/SearchUIUtils.ts | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 2d75237f5156..a269e67d6f6d 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -91,8 +91,8 @@ import {
getReportName,
getReportOrDraftReport,
getSearchReportName,
+ hasHeldExpenses,
hasInvoiceReports,
- hasOnlyHeldExpenses,
isAllowedToApproveExpenseReport as isAllowedToApproveExpenseReportUtils,
isArchivedReport,
isClosedReport,
@@ -1186,7 +1186,8 @@ function getActions(
const canBePaid = canIOUBePaid(report, chatReport, policy, allReportTransactions, false, chatReportRNVP, invoiceReceiverPolicy);
const shouldOnlyShowElsewhere = !canBePaid && canIOUBePaid(report, chatReport, policy, allReportTransactions, true, chatReportRNVP, invoiceReceiverPolicy);
- if ((canBePaid || shouldOnlyShowElsewhere) && !hasOnlyHeldExpenses(report.reportID, allReportTransactions)) {
+ // We're not supporting pay partial amount on search page now.
+ if ((canBePaid || shouldOnlyShowElsewhere) && !hasHeldExpenses(report.reportID, allReportTransactions)) {
allActions.push(CONST.SEARCH.ACTION_TYPES.PAY);
}
@@ -1194,18 +1195,20 @@ function getActions(
allActions.push(CONST.SEARCH.ACTION_TYPES.EXPORT_TO_ACCOUNTING);
}
- if (isClosedReport(report)) {
+ if (isClosedReport(report) && !(canBePaid || shouldOnlyShowElsewhere)) {
return allActions.length > 0 ? allActions : [CONST.SEARCH.ACTION_TYPES.DONE];
}
const hasOnlyPendingCardOrScanningTransactions = allReportTransactions.length > 0 && allReportTransactions.every((t) => isScanning(t) || isPending(t));
const isAllowedToApproveExpenseReport = isAllowedToApproveExpenseReportUtils(report, undefined, policy);
+
+ // We're not supporting approve partial amount on search page now
if (
canApproveIOU(report, policy, allReportTransactions) &&
isAllowedToApproveExpenseReport &&
!hasOnlyPendingCardOrScanningTransactions &&
- !hasOnlyHeldExpenses(report.reportID, allReportTransactions)
+ !hasHeldExpenses(report.reportID, allReportTransactions)
) {
allActions.push(CONST.SEARCH.ACTION_TYPES.APPROVE);
}
From 1bf3dc8a75e95526c2f2f059d7415ddef7dfb846 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Thu, 13 Nov 2025 14:30:18 +0700
Subject: [PATCH 08/20] fix prettier
---
src/libs/SearchUIUtils.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index a269e67d6f6d..fd2923f71d9f 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -1202,7 +1202,7 @@ function getActions(
const hasOnlyPendingCardOrScanningTransactions = allReportTransactions.length > 0 && allReportTransactions.every((t) => isScanning(t) || isPending(t));
const isAllowedToApproveExpenseReport = isAllowedToApproveExpenseReportUtils(report, undefined, policy);
-
+
// We're not supporting approve partial amount on search page now
if (
canApproveIOU(report, policy, allReportTransactions) &&
From 0ec168eb88cf94032ad94ae50f2f9900254a8a7b Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Wed, 19 Nov 2025 15:33:18 +0700
Subject: [PATCH 09/20] fix animation on mounted
---
src/components/AnimatedCollapsible/index.tsx | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/components/AnimatedCollapsible/index.tsx b/src/components/AnimatedCollapsible/index.tsx
index 22a0b10a5ac4..adbd02294d64 100644
--- a/src/components/AnimatedCollapsible/index.tsx
+++ b/src/components/AnimatedCollapsible/index.tsx
@@ -115,9 +115,10 @@ function AnimatedCollapsible({
const descriptionAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: descriptionOpacity.get(),
- height: descriptionAnimatedHeight.get(),
+ // The row is collapsed by default, so we don't need to animate the height when it's not expanded
+ height: isRendered ? descriptionAnimatedHeight.get() : undefined,
};
- }, []);
+ }, [isRendered]);
const contentAnimatedStyle = useAnimatedStyle(() => {
return {
@@ -152,7 +153,7 @@ function AnimatedCollapsible({
{!!description && !isExpanded && (
{
const height = e.nativeEvent.layout.height;
if (height) {
From ba5eb19ee6b1df0113eb370e9cb5938124319eb9 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Wed, 26 Nov 2025 00:43:25 +0700
Subject: [PATCH 10/20] fix ts error
---
.../Search/TransactionGroupListItem.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index 1a5da89f7ce4..4b01d763fab0 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -311,7 +311,7 @@ function TransactionGroupListItem({
: undefined);
const hasViolations = transactions.some((transaction) => {
- const transactionViolations = getTransactionViolations(transaction, violations);
+ const transactionViolations = getTransactionViolations(transaction, violations, currentUserDetails.email ?? '', undefined, undefined);
return transactionViolations && transactionViolations.length > 0;
});
From 20c290aec6c68f1570bb837bbb675bbb081dffa7 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Fri, 28 Nov 2025 00:00:43 +0700
Subject: [PATCH 11/20] implement violation message to new UI
---
.../Search/ExpenseReportListItemRow.tsx | 175 ++++++++++--------
.../SelectionListWithSections/types.ts | 3 +
src/libs/SearchUIUtils.ts | 10 +
3 files changed, 114 insertions(+), 74 deletions(-)
diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
index 672ea173324e..5b85b21601f3 100644
--- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
+++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
@@ -2,13 +2,18 @@ import React, {useMemo} from 'react';
import {View} from 'react-native';
import type {ColorValue, StyleProp, ViewStyle} from 'react-native';
import Checkbox from '@components/Checkbox';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
import ReportActionAvatars from '@components/ReportActionAvatars';
import ReportSearchHeader from '@components/ReportSearchHeader';
import type {ExpenseReportListItemType} from '@components/SelectionListWithSections/types';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import {isSettled} from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {Policy} from '@src/types/onyx';
@@ -57,6 +62,7 @@ function ExpenseReportListItemRow({
const styles = useThemeStyles();
const theme = useTheme();
const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();
+ const {translate} = useLocalize();
const {total, currency} = useMemo(() => {
let reportTotal = item.total ?? 0;
@@ -80,6 +86,24 @@ function ExpenseReportListItemRow({
// Calculate the correct border color for avatars based on hover and focus states
const finalAvatarBorderColor = isHovered && !isFocused ? theme.border : avatarBorderColor;
+ const getDescription = useMemo(() => {
+ if (!item?.hasVisibleViolations || isSettled(item.reportID)) {
+ return;
+ }
+ return (
+
+
+ {translate('reportViolations.reportContainsExpensesWithViolations')}
+
+ );
+ }, [item?.hasVisibleViolations, item.reportID, styles.alignItemsCenter, styles.flexRow, styles.mr1, styles.mt2, styles.textDanger, styles.textMicro, theme.danger, translate]);
+
if (!isLargeScreenWidth) {
return (
@@ -125,85 +149,88 @@ function ExpenseReportListItemRow({
}
return (
-
-
- {!!canSelectMultiple && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {!!item.from && (
-
+
+
+ {!!canSelectMultiple && (
+
)}
-
-
- {!!item.to && (
-
+
- )}
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {!!item.from && (
+
+ )}
+
+
+ {!!item.to && (
+
+ )}
+
+
+
+
+
+
+
+ {getDescription}
);
}
diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts
index 9d373044b83b..792d8486ea78 100644
--- a/src/components/SelectionListWithSections/types.ts
+++ b/src/components/SelectionListWithSections/types.ts
@@ -375,6 +375,9 @@ type TransactionGroupListItemType = ListItem & {
/** The hash of the query to get the transactions data */
transactionsQueryJSON?: SearchQueryJSON;
+
+ /** Whether the report has visible violations for user */
+ hasVisibleViolations?: boolean;
};
// eslint-disable-next-line @typescript-eslint/no-deprecated
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 9c8250100dfa..1b3df7ccad40 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -94,6 +94,7 @@ import {
getReportOrDraftReport,
getReportStatusTranslation,
getSearchReportName,
+ hasAnyViolations,
hasHeldExpenses,
hasInvoiceReports,
isAllowedToApproveExpenseReport as isAllowedToApproveExpenseReportUtils,
@@ -122,6 +123,7 @@ import {
isViolationDismissed,
} from './TransactionUtils';
import shouldShowTransactionYear from './TransactionUtils/shouldShowTransactionYear';
+import ViolationsUtils from './Violations/ViolationsUtils';
type ColumnSortMapping = Partial>;
type ColumnVisibility = Partial>;
@@ -1527,6 +1529,13 @@ function getReportSections(
// eslint-disable-next-line @typescript-eslint/no-deprecated
const formattedStatus = getReportStatusTranslation({stateNum: reportItem.stateNum, statusNum: reportItem.statusNum, translate: translateLocal});
+ const allReportTransactions = getTransactionsForReport(data, reportItem.reportID);
+ const policy = getPolicyFromKey(data, reportItem);
+
+ const hasAnyViolationsForReport = hasAnyViolations(reportItem.reportID, allViolations, allReportTransactions, currentUserEmail, reportItem, policy);
+ const hasVisibleViolationsForReport =
+ hasAnyViolationsForReport && ViolationsUtils.hasVisibleViolationsForUser(reportItem, allViolations, currentUserEmail, policy, allReportTransactions);
+
reportIDToTransactions[reportKey] = {
...reportItem,
action: allActions.at(0) ?? CONST.SEARCH.ACTION_TYPES.VIEW,
@@ -1541,6 +1550,7 @@ function getReportSections(
transactions,
...(reportPendingAction ? {pendingAction: reportPendingAction} : {}),
shouldShowYear: doesDataContainAPastYearReport,
+ hasVisibleViolations: hasVisibleViolationsForReport,
};
if (isIOUReport) {
From 755d896aa2a0f1683599904ac5775ee313577de4 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Tue, 2 Dec 2025 16:40:25 +0700
Subject: [PATCH 12/20] update getAction function
---
src/libs/SearchUIUtils.ts | 28 +++-------------------------
1 file changed, 3 insertions(+), 25 deletions(-)
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 444762891b84..f93ea9c16f10 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -1109,7 +1109,7 @@ function getTransactionsSections(
formatPhoneNumber,
report,
);
- const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, currentUserEmail);
+ const allActions = getActions(data, allViolations, key, currentSearch, currentUserEmail);
const transactionSection: TransactionListItemType = {
...transactionItem,
keyForList: transactionItem.transactionID,
@@ -1204,7 +1204,6 @@ function getActions(
allViolations: OnyxCollection,
key: string,
currentSearch: SearchKey,
- currentAccountID: number | undefined,
currentUserEmail: string,
reportActions: OnyxTypes.ReportAction[] = [],
): SearchTransactionAction[] {
@@ -1258,27 +1257,6 @@ function getActions(
const chatReportRNVP = data[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.chatReportID}`] ?? undefined;
const isChatReportArchived = isArchivedReport(chatReportRNVP);
-<<<<<<< HEAD
-=======
- const hasAnyViolationsForReport = hasAnyViolations(report.reportID, allViolations, currentAccountID ?? CONST.DEFAULT_NUMBER_ID, currentUserEmail, allReportTransactions, report, policy);
- const hasVisibleViolationsForReport =
- hasAnyViolationsForReport &&
- ViolationsUtils.hasVisibleViolationsForUser(report, allViolations, currentUserEmail, currentAccountID ?? CONST.DEFAULT_NUMBER_ID, policy, allReportTransactions);
-
- // Only check for violations if we need to (when user has permission to review)
- if ((isSubmitter || isApprover || isAdmin) && hasVisibleViolationsForReport) {
- if (
- isSubmitter &&
- !isApprover &&
- !isAdmin &&
- !canReview(report, allViolations, isIOUReportArchived || isChatReportArchived, currentUserEmail, currentAccountID ?? CONST.DEFAULT_NUMBER_ID, policy, allReportTransactions)
- ) {
- allActions.push(CONST.SEARCH.ACTION_TYPES.VIEW);
- } else {
- allActions.push(CONST.SEARCH.ACTION_TYPES.REVIEW);
- }
- }
->>>>>>> main
// Submit/Approve/Pay can only be taken on transactions if the transaction is the only one on the report, otherwise `View` is the only option.
// If this condition is not met, return early for performance reasons
if (isTransaction && !isOneTransactionReport(report)) {
@@ -1548,7 +1526,7 @@ function getReportSections(
if (shouldShow) {
const reportPendingAction = reportItem?.pendingAction ?? reportItem?.pendingFields?.preview;
const shouldShowBlankTo = !reportItem || isOpenExpenseReport(reportItem);
- const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, currentUserEmail, actions);
+ const allActions = getActions(data, allViolations, key, currentSearch, currentUserEmail, actions);
const fromDetails =
data.personalDetailsList?.[reportItem.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID] ??
@@ -1612,7 +1590,7 @@ function getReportSections(
report,
);
- const allActions = getActions(data, allViolations, key, currentSearch, currentAccountID, currentUserEmail, actions);
+ const allActions = getActions(data, allViolations, key, currentSearch, currentUserEmail, actions);
const transaction = {
...transactionItem,
action: allActions.at(0) ?? CONST.SEARCH.ACTION_TYPES.VIEW,
From 378ef3da34421f630313edc957a7c70be2a6dbd1 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Tue, 2 Dec 2025 16:44:10 +0700
Subject: [PATCH 13/20] fix new function error
---
src/libs/SearchUIUtils.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index f93ea9c16f10..0952dffe469a 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -1543,9 +1543,9 @@ function getReportSections(
const allReportTransactions = getTransactionsForReport(data, reportItem.reportID);
const policy = getPolicyFromKey(data, reportItem);
- const hasAnyViolationsForReport = hasAnyViolations(reportItem.reportID, allViolations, allReportTransactions, currentUserEmail, reportItem, policy);
+ const hasAnyViolationsForReport = hasAnyViolations(reportItem.reportID, allViolations, currentAccountID ?? CONST.DEFAULT_NUMBER_ID, currentUserEmail, allReportTransactions, reportItem, policy);
const hasVisibleViolationsForReport =
- hasAnyViolationsForReport && ViolationsUtils.hasVisibleViolationsForUser(reportItem, allViolations, currentUserEmail, policy, allReportTransactions);
+ hasAnyViolationsForReport && ViolationsUtils.hasVisibleViolationsForUser(reportItem, allViolations, currentUserEmail, currentAccountID ?? CONST.DEFAULT_NUMBER_ID, policy, allReportTransactions);
reportIDToTransactions[reportKey] = {
...reportItem,
From 2b95c6286d94a3f7d2757b1e22f14767e3feec75 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Tue, 2 Dec 2025 17:04:23 +0700
Subject: [PATCH 14/20] update new UI
---
.../Search/ExpenseReportListItem.tsx | 70 +++++--
.../Search/ExpenseReportListItemRow.tsx | 182 ++++++++----------
.../Search/TransactionGroupListItem.tsx | 31 +--
src/libs/SearchUIUtils.ts | 13 +-
4 files changed, 149 insertions(+), 147 deletions(-)
diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
index d4bc316f520d..9aca5ece82f8 100644
--- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
@@ -1,13 +1,19 @@
import React, {useCallback, useMemo} from 'react';
+import {View} from 'react-native';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
import {useSearchContext} from '@components/Search/SearchContext';
import BaseListItem from '@components/SelectionListWithSections/BaseListItem';
import type {ExpenseReportListItemProps, ExpenseReportListItemType, ListItem} from '@components/SelectionListWithSections/types';
+import Text from '@components/Text';
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
+import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {handleActionButtonPress} from '@libs/actions/Search';
+import {isSettled} from '@libs/ReportUtils';
import variables from '@styles/variables';
import ONYXKEYS from '@src/ONYXKEYS';
import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
@@ -31,7 +37,7 @@ function ExpenseReportListItem({
const reportItem = item as unknown as ExpenseReportListItemType;
const styles = useThemeStyles();
const theme = useTheme();
-
+ const {translate} = useLocalize();
const {isLargeScreenWidth} = useResponsiveLayout();
const {currentSearchHash, currentSearchKey} = useSearchContext();
const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true});
@@ -100,6 +106,35 @@ function ExpenseReportListItem({
backgroundColor: theme.highlightBG,
});
+ const getDescription = useMemo(() => {
+ if (!reportItem?.hasVisibleViolations || isSettled(reportItem.reportID)) {
+ return;
+ }
+ return (
+
+
+ {translate('reportViolations.reportContainsExpensesWithViolations')}
+
+ );
+ }, [
+ reportItem?.hasVisibleViolations,
+ reportItem.reportID,
+ styles.alignItemsCenter,
+ styles.flexRow,
+ styles.mr1,
+ styles.mt2,
+ styles.textDanger,
+ styles.textMicro,
+ theme.danger,
+ translate,
+ ]);
+
return (
({
shouldSyncFocus={shouldSyncFocus}
hoverStyle={item.isSelected && styles.activeComponentBG}
pressableWrapperStyle={[styles.mh5, animatedHighlightStyle]}
- shouldShowRightCaret={isLargeScreenWidth}
+ shouldShowRightCaret={false}
shouldUseDefaultRightHandSideCheckmark={false}
>
{(hovered) => (
-
+
+
+ {getDescription}
+
)}
);
diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
index 12daac48cc97..9f1ce8255801 100644
--- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
+++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
@@ -7,13 +7,10 @@ import * as Expensicons from '@components/Icon/Expensicons';
import ReportActionAvatars from '@components/ReportActionAvatars';
import ReportSearchHeader from '@components/ReportSearchHeader';
import type {ExpenseReportListItemType} from '@components/SelectionListWithSections/types';
-import Text from '@components/Text';
-import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import {isSettled} from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {Policy} from '@src/types/onyx';
@@ -60,7 +57,6 @@ function ExpenseReportListItemRow({
const styles = useThemeStyles();
const theme = useTheme();
const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();
- const {translate} = useLocalize();
const {total, currency} = useMemo(() => {
let reportTotal = item.total ?? 0;
@@ -86,24 +82,6 @@ function ExpenseReportListItemRow({
StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, !!isFocused || !!isHovered, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG)?.backgroundColor ??
theme.highlightBG;
- const getDescription = useMemo(() => {
- if (!item?.hasVisibleViolations || isSettled(item.reportID)) {
- return;
- }
- return (
-
-
- {translate('reportViolations.reportContainsExpensesWithViolations')}
-
- );
- }, [item?.hasVisibleViolations, item.reportID, styles.alignItemsCenter, styles.flexRow, styles.mr1, styles.mt2, styles.textDanger, styles.textMicro, theme.danger, translate]);
-
if (!isLargeScreenWidth) {
return (
@@ -149,88 +127,94 @@ function ExpenseReportListItemRow({
}
return (
-
-
-
- {!!canSelectMultiple && (
-
+
+ {!!canSelectMultiple && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!!item.from && (
+
)}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {!!item.from && (
-
- )}
-
-
- {!!item.to && (
-
- )}
-
-
-
-
-
-
+
+ {!!item.to && (
+
-
+ )}
+
+
+
+
+
+
+
+
+
- {getDescription}
);
}
diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
index b228f2342765..cb60b7796048 100644
--- a/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/TransactionGroupListItem.tsx
@@ -6,8 +6,6 @@ import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx as originalUseOnyx} from 'react-native-onyx';
import AnimatedCollapsible from '@components/AnimatedCollapsible';
import {getButtonRole} from '@components/Button/utils';
-import Icon from '@components/Icon';
-import * as Expensicons from '@components/Icon/Expensicons';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {PressableWithFeedback} from '@components/Pressable';
import {useSearchContext} from '@components/Search/SearchContext';
@@ -22,7 +20,6 @@ import type {
TransactionReportGroupListItemType,
TransactionWithdrawalIDGroupListItemType,
} from '@components/SelectionListWithSections/types';
-import Text from '@components/Text';
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
@@ -34,9 +31,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {search} from '@libs/actions/Search';
import type {TransactionPreviewData} from '@libs/actions/Search';
-import {isSettled} from '@libs/ReportUtils';
import {getSections} from '@libs/SearchUIUtils';
-import {getTransactionViolations} from '@libs/TransactionUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -72,7 +67,7 @@ function TransactionGroupListItem({
const groupItem = item as unknown as TransactionGroupListItemType;
const theme = useTheme();
const styles = useThemeStyles();
- const {formatPhoneNumber, translate} = useLocalize();
+ const {formatPhoneNumber} = useLocalize();
const {selectedTransactions} = useSearchContext();
const {isLargeScreenWidth} = useResponsiveLayout();
const currentUserDetails = useCurrentUserPersonalDetails();
@@ -311,29 +306,6 @@ function TransactionGroupListItem({
? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE
: undefined);
- const hasViolations = transactions.some((transaction) => {
- const transactionViolations = getTransactionViolations(transaction, violations, currentUserDetails.email ?? '', undefined, undefined);
- return transactionViolations && transactionViolations.length > 0;
- });
-
- const getDescription = useMemo(() => {
- if (!hasViolations || isSettled(item.reportID)) {
- return;
- }
- return (
-
-
- {translate('reportViolations.reportContainsExpensesWithViolations')}
-
- );
- }, [hasViolations, item.reportID, styles.alignItemsCenter, styles.flexRow, styles.ml3, styles.mr1, styles.mv1, styles.textDanger, styles.textMicro, theme.danger, translate]);
-
return (
({
header={getHeader(hovered)}
onPress={onExpandIconPress}
expandButtonStyle={styles.pv4Half}
- description={getDescription}
shouldShowToggleButton={isLargeScreenWidth}
>
Date: Tue, 2 Dec 2025 17:10:37 +0700
Subject: [PATCH 15/20] fix test
---
tests/unit/Search/SearchUIUtilsTest.ts | 36 ++++++++++++++------------
1 file changed, 20 insertions(+), 16 deletions(-)
diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts
index 7ab4156e5e24..704ef1faa55f 100644
--- a/tests/unit/Search/SearchUIUtilsTest.ts
+++ b/tests/unit/Search/SearchUIUtilsTest.ts
@@ -1027,6 +1027,7 @@ const transactionReportGroupListItems = [
displayName: 'Admin',
login: 'admin@policy.com',
},
+ hasVisibleViolations: false,
isOneTransactionReport: true,
isWaitingOnBankAccount: false,
keyForList: '123456789',
@@ -1119,6 +1120,7 @@ const transactionReportGroupListItems = [
displayName: 'Admin',
login: adminEmail,
},
+ hasVisibleViolations: true,
isOneTransactionReport: true,
isWaitingOnBankAccount: false,
keyForList: '11111',
@@ -1219,6 +1221,7 @@ const transactionReportGroupListItems = [
formattedFrom: 'Admin',
formattedStatus: 'Outstanding',
formattedTo: 'Approver',
+ hasVisibleViolations: false,
isOneTransactionReport: false,
isOwnPolicyExpenseChat: false,
isWaitingOnBankAccount: false,
@@ -1271,6 +1274,7 @@ const transactionReportGroupListItems = [
displayName: 'Admin',
login: 'admin@policy.com',
},
+ hasVisibleViolations: false,
isOneTransactionReport: true,
isWaitingOnBankAccount: false,
keyForList: reportID5,
@@ -1484,15 +1488,15 @@ describe('SearchUIUtils', () => {
});
describe('Test getAction', () => {
test('Should return `View` action for an invalid key', () => {
- const action = SearchUIUtils.getActions(searchResults.data, {}, 'invalid_key', CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(searchResults.data, {}, 'invalid_key', CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
});
test('Should return `Submit` action for transaction on policy with delayed submission and no violations', () => {
- let action = SearchUIUtils.getActions(searchResults.data, {}, `report_${reportID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ let action = SearchUIUtils.getActions(searchResults.data, {}, `report_${reportID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.SUBMIT);
- action = SearchUIUtils.getActions(searchResults.data, {}, `transactions_${transactionID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ action = SearchUIUtils.getActions(searchResults.data, {}, `transactions_${transactionID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.SUBMIT);
});
@@ -1515,11 +1519,11 @@ describe('SearchUIUtils', () => {
managerID: adminAccountID,
},
};
- expect(SearchUIUtils.getActions(localSearchResults, allViolations, `report_${reportID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, submitterAccountID, '').at(0)).toStrictEqual(
+ expect(SearchUIUtils.getActions(localSearchResults, allViolations, `report_${reportID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0)).toStrictEqual(
CONST.SEARCH.ACTION_TYPES.VIEW,
);
expect(
- SearchUIUtils.getActions(localSearchResults, allViolations, `transactions_${transactionID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, submitterAccountID, '').at(0),
+ SearchUIUtils.getActions(localSearchResults, allViolations, `transactions_${transactionID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0),
).toStrictEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
});
@@ -1538,7 +1542,7 @@ describe('SearchUIUtils', () => {
},
};
- const action = SearchUIUtils.getActions(localSearchResults, {}, paidReportID, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(localSearchResults, {}, paidReportID, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.PAID);
});
@@ -1562,7 +1566,7 @@ describe('SearchUIUtils', () => {
},
};
- const action = SearchUIUtils.getActions(localSearchResults, {}, paidReportID, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(localSearchResults, {}, paidReportID, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.PAID);
});
@@ -1578,7 +1582,7 @@ describe('SearchUIUtils', () => {
},
};
- const action = SearchUIUtils.getActions(localSearchResults, {}, `report_${closedReportID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(localSearchResults, {}, `report_${closedReportID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.PAY);
});
@@ -1596,13 +1600,13 @@ describe('SearchUIUtils', () => {
},
};
- const action = SearchUIUtils.getActions(localSearchResults, {}, `report_${closedReportID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(localSearchResults, {}, `report_${closedReportID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.DONE);
});
test('Should return `View` action for non-money request reports', () => {
- const action = SearchUIUtils.getActions(searchResults.data, {}, `report_${reportID4}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(searchResults.data, {}, `report_${reportID4}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
});
@@ -1616,7 +1620,7 @@ describe('SearchUIUtils', () => {
reportID: 'non_existent_report',
},
};
- const action = SearchUIUtils.getActions(localSearchResults, {}, `transactions_${orphanedTransactionID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(localSearchResults, {}, `transactions_${orphanedTransactionID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
});
test('Should return `View` action for a transaction in a multi-transaction report', () => {
@@ -1634,14 +1638,14 @@ describe('SearchUIUtils', () => {
reportID: multiTransactionReportID,
},
};
- const action = SearchUIUtils.getActions(localSearchResults, {}, `transactions_${multiTransactionID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(localSearchResults, {}, `transactions_${multiTransactionID}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toStrictEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
});
test('Should return `Pay` action for an IOU report ready to be paid', async () => {
Onyx.merge(ONYXKEYS.SESSION, {accountID: adminAccountID});
await waitForBatchedUpdates();
const iouReportKey = `report_${reportID3}`;
- const action = SearchUIUtils.getActions(searchResults.data, {}, iouReportKey, CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(searchResults.data, {}, iouReportKey, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toEqual(CONST.SEARCH.ACTION_TYPES.PAY);
});
});
@@ -2419,10 +2423,10 @@ describe('SearchUIUtils', () => {
Onyx.merge(ONYXKEYS.SESSION, {accountID: overlimitApproverAccountID});
searchResults.data[`policy_${policyID}`].role = CONST.POLICY.ROLE.USER;
return waitForBatchedUpdates().then(() => {
- let action = SearchUIUtils.getActions(searchResults.data, allViolations, `report_${reportID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, overlimitApproverAccountID, '').at(0);
+ let action = SearchUIUtils.getActions(searchResults.data, allViolations, `report_${reportID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
- action = SearchUIUtils.getActions(searchResults.data, allViolations, `transactions_${transactionID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, overlimitApproverAccountID, '').at(0);
+ action = SearchUIUtils.getActions(searchResults.data, allViolations, `transactions_${transactionID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
});
});
@@ -2537,7 +2541,7 @@ describe('SearchUIUtils', () => {
},
};
return waitForBatchedUpdates().then(() => {
- const action = SearchUIUtils.getActions(result.data, allViolations, 'report_6523565988285061', CONST.SEARCH.SEARCH_KEYS.EXPENSES, adminAccountID, '').at(0);
+ const action = SearchUIUtils.getActions(result.data, allViolations, 'report_6523565988285061', CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0);
expect(action).toEqual(CONST.SEARCH.ACTION_TYPES.APPROVE);
});
});
From 0b7bad3a77d83e24a86ebceff0ec8994111d7c82 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Tue, 2 Dec 2025 17:13:16 +0700
Subject: [PATCH 16/20] fix eslint
---
.../Search/ExpenseReportListItemRow.tsx | 5 +++--
tests/unit/Search/SearchUIUtilsTest.ts | 6 +++---
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
index 9f1ce8255801..4bc87033dc2e 100644
--- a/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
+++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItemRow.tsx
@@ -3,10 +3,10 @@ import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import Checkbox from '@components/Checkbox';
import Icon from '@components/Icon';
-import * as Expensicons from '@components/Icon/Expensicons';
import ReportActionAvatars from '@components/ReportActionAvatars';
import ReportSearchHeader from '@components/ReportSearchHeader';
import type {ExpenseReportListItemType} from '@components/SelectionListWithSections/types';
+import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
@@ -57,6 +57,7 @@ function ExpenseReportListItemRow({
const styles = useThemeStyles();
const theme = useTheme();
const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();
+ const expensifyIcons = useMemoizedLazyExpensifyIcons(['ArrowRight'] as const);
const {total, currency} = useMemo(() => {
let reportTotal = item.total ?? 0;
@@ -208,7 +209,7 @@ function ExpenseReportListItemRow({
{
expect(SearchUIUtils.getActions(localSearchResults, allViolations, `report_${reportID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0)).toStrictEqual(
CONST.SEARCH.ACTION_TYPES.VIEW,
);
- expect(
- SearchUIUtils.getActions(localSearchResults, allViolations, `transactions_${transactionID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0),
- ).toStrictEqual(CONST.SEARCH.ACTION_TYPES.VIEW);
+ expect(SearchUIUtils.getActions(localSearchResults, allViolations, `transactions_${transactionID2}`, CONST.SEARCH.SEARCH_KEYS.EXPENSES, '').at(0)).toStrictEqual(
+ CONST.SEARCH.ACTION_TYPES.VIEW,
+ );
});
test('Should return `Paid` action for a manually settled report', () => {
From 6323f27cb8cd3f0b01aabb823fbb46da2299bb29 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Tue, 2 Dec 2025 17:20:11 +0700
Subject: [PATCH 17/20] fix eslint
---
.../Search/ExpenseReportListItem.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
index 9aca5ece82f8..033ad1a77b99 100644
--- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
@@ -1,12 +1,12 @@
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import Icon from '@components/Icon';
-import * as Expensicons from '@components/Icon/Expensicons';
import {useSearchContext} from '@components/Search/SearchContext';
import BaseListItem from '@components/SelectionListWithSections/BaseListItem';
import type {ExpenseReportListItemProps, ExpenseReportListItemType, ListItem} from '@components/SelectionListWithSections/types';
import Text from '@components/Text';
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
+import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
@@ -43,6 +43,7 @@ function ExpenseReportListItem({
const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true});
const [snapshot] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchHash}`, {canBeMissing: true});
const [isActionLoading] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportItem.reportID}`, {canBeMissing: true, selector: isActionLoadingSelector});
+ const expensifyIcons = useMemoizedLazyExpensifyIcons(['DotIndicator'] as const);
const snapshotData = snapshot?.data;
@@ -113,7 +114,7 @@ function ExpenseReportListItem({
return (
Date: Tue, 2 Dec 2025 17:33:28 +0700
Subject: [PATCH 18/20] update dependency
---
.../SelectionListWithSections/Search/ExpenseReportListItem.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
index 033ad1a77b99..cfb4b3f4c7f6 100644
--- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
+++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx
@@ -134,6 +134,7 @@ function ExpenseReportListItem({
styles.textMicro,
theme.danger,
translate,
+ expensifyIcons.DotIndicator,
]);
return (
From 71707aac55f00f9617595e6115dc627351ab73f7 Mon Sep 17 00:00:00 2001
From: nkdengineer
Date: Tue, 2 Dec 2025 17:45:41 +0700
Subject: [PATCH 19/20] remove Expensicons
---
src/components/AnimatedCollapsible/index.tsx | 5 +++--
.../MoneyRequestReportPreviewContent.tsx | 7 ++++---
.../SelectionListWithSections/Search/ActionCell.tsx | 5 +++--
3 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/components/AnimatedCollapsible/index.tsx b/src/components/AnimatedCollapsible/index.tsx
index adbd02294d64..114dc045f811 100644
--- a/src/components/AnimatedCollapsible/index.tsx
+++ b/src/components/AnimatedCollapsible/index.tsx
@@ -5,9 +5,9 @@ import type {StyleProp, ViewStyle} from 'react-native';
import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated';
import {scheduleOnRN} from 'react-native-worklets';
import Icon from '@components/Icon';
-import * as Expensicons from '@components/Icon/Expensicons';
import {easing} from '@components/Modal/ReanimatedModal/utils';
import {PressableWithFeedback} from '@components/Pressable';
+import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
@@ -70,6 +70,7 @@ function AnimatedCollapsible({
}: AnimatedCollapsibleProps) {
const theme = useTheme();
const styles = useThemeStyles();
+ const expensifyIcons = useMemoizedLazyExpensifyIcons(['UpArrow', 'DownArrow'] as const);
const contentHeight = useSharedValue(0);
const descriptionHeight = useSharedValue(0);
const hasExpanded = useSharedValue(isExpanded);
@@ -141,7 +142,7 @@ function AnimatedCollapsible({
>
{({hovered}) => (
({
@@ -719,7 +720,7 @@ function MoneyRequestReportPreviewContent({
disabledStyle={[styles.cursorDefault, styles.buttonOpacityDisabled]}
>
Date: Wed, 3 Dec 2025 10:22:59 +0700
Subject: [PATCH 20/20] use new method
---
.../MoneyRequestReportPreviewContent.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
index e52f7233454e..3a2419ca318e 100644
--- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx
@@ -43,12 +43,12 @@ import {getTotalAmountForIOUReportPreviewButton} from '@libs/MoneyRequestReportU
import Navigation from '@libs/Navigation/Navigation';
import Performance from '@libs/Performance';
import {getConnectedIntegration, hasDynamicExternalWorkflow} from '@libs/PolicyUtils';
+import {getInvoicePayerName} from '@libs/ReportNameUtils';
import getReportPreviewAction from '@libs/ReportPreviewActionUtils';
import {
areAllRequestsBeingSmartScanned as areAllRequestsBeingSmartScannedReportUtils,
getAddExpenseDropdownOptions,
getDisplayNameForParticipant,
- getInvoicePayerName,
getMoneyReportPreviewName,
getMoneyRequestSpendBreakdown,
getNonHeldAndFullAmount,