diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index 1165c5da5e505..f65d5849b00e5 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -434,28 +434,18 @@ function OptionRowLHN({
/>
)}
- {!brickRoadIndicator &&
- !!optionItem.isPinned &&
- (isProduction ? (
-
-
-
- ) : (
-
+
- ))}
+
+ )}
);
diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx
index afaaee65c4098..79d5f2e176590 100644
--- a/src/components/LHNOptionsList/OptionRowLHNData.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx
@@ -114,6 +114,7 @@ function OptionRowLHNData({
fullReport,
reportAttributes?.brickRoadStatus,
reportAttributes?.actionBadge,
+ reportAttributes?.actionBadgeReportActionID,
reportAttributes?.reportName,
areReportErrorsEqual,
oneTransactionThreadReport,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 434e15aa7fbfa..78d0bfea31e85 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -833,6 +833,7 @@ type OptionData = {
allReportErrors?: Errors;
brickRoadIndicator?: ValueOf | '' | null;
actionBadge?: ValueOf;
+ actionBadgeReportActionID?: string;
tooltipText?: string | null;
alternateTextMaxLines?: number;
boldStyle?: boolean;
@@ -9137,6 +9138,80 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV
});
}
+/**
+ * Returns the reportID of the first child expense report that has violations under the same policy,
+ * or undefined if none found. Used to find the REPORT_PREVIEW action to deep-link to.
+ */
+function getViolatingReportIDForLHN(report: OnyxEntry, transactionViolations: OnyxCollection): string | undefined {
+ if (!report || !isPolicyExpenseChat(report)) {
+ return undefined;
+ }
+
+ if (!isCurrentUserSubmitter(report)) {
+ return undefined;
+ }
+ if (!report.policyID || !reportsByPolicyID) {
+ return undefined;
+ }
+
+ const potentialReports = Object.values(reportsByPolicyID[report.policyID] ?? {}) ?? [];
+ const violatingReport = potentialReports.find((potentialReport) => {
+ if (!potentialReport) {
+ return false;
+ }
+ const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${potentialReport.policyID}`];
+ const transactions = getReportTransactions(potentialReport.reportID);
+
+ if (!isOpenOrProcessingReport(potentialReport)) {
+ return false;
+ }
+
+ return (
+ !isInvoiceReport(potentialReport) &&
+ ViolationsUtils.hasVisibleViolationsForUser(
+ potentialReport,
+ transactionViolations,
+ currentUserEmail ?? '',
+ currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
+ policy,
+ transactions,
+ ) &&
+ (hasViolations(
+ potentialReport.reportID,
+ transactionViolations,
+ currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserEmail ?? '',
+ true,
+ transactions,
+ potentialReport,
+ policy,
+ ) ||
+ hasWarningTypeViolations(
+ potentialReport.reportID,
+ transactionViolations,
+ currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserEmail ?? '',
+ true,
+ transactions,
+ potentialReport,
+ policy,
+ ) ||
+ hasNoticeTypeViolations(
+ potentialReport.reportID,
+ transactionViolations,
+ currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserEmail ?? '',
+ true,
+ transactions,
+ potentialReport,
+ policy,
+ ))
+ );
+ });
+
+ return violatingReport?.reportID;
+}
+
/**
* Checks to see if a report contains a violation
*/
@@ -12714,7 +12789,7 @@ function generateReportAttributes({
const hasErrors = Object.entries(reportErrors ?? {}).length > 0;
const oneTransactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActionsList);
const parentReportAction = report?.parentReportActionID ? parentReportActionsList?.[report.parentReportActionID] : undefined;
- const {reason, actionBadge} = getReasonAndReportActionThatRequiresAttention(report, parentReportAction, isReportArchived) ?? {};
+ const {reason, reportAction: attentionReportAction, actionBadge} = getReasonAndReportActionThatRequiresAttention(report, parentReportAction, isReportArchived) ?? {};
return {
hasViolationsToDisplayInLHN,
@@ -12725,6 +12800,7 @@ function generateReportAttributes({
parentReportAction,
requiresAttention: !!reason,
actionBadge,
+ actionBadgeReportActionID: attentionReportAction?.reportActionID,
};
}
@@ -13378,6 +13454,7 @@ export {
shouldDisableThread,
shouldDisplayThreadReplies,
shouldDisplayViolationsRBRInLHN,
+ getViolatingReportIDForLHN,
shouldReportBeInOptionList,
shouldReportShowSubscript,
shouldShowFlagComment,
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 53d613cb5eafe..cd0a129ede50b 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -125,6 +125,7 @@ import {
isInviteOrRemovedAction,
isOldDotReportAction,
isRenamedAction,
+ isReportPreviewAction,
isTagModificationAction,
isTaskAction,
} from './ReportActionsUtils';
@@ -151,6 +152,7 @@ import {
getReportParticipantsTitle,
getReportSubtitlePrefix,
getUnreportedTransactionMessage,
+ getViolatingReportIDForLHN,
getWorkspaceNameUpdatedMessage,
hasReportErrorsOtherThanFailedReceipt,
isAdminRoom,
@@ -635,9 +637,12 @@ function getReasonAndReportActionThatHasRedBrickRoad(
return null;
}
- if (shouldDisplayViolationsRBRInLHN(report, transactionViolations)) {
+ const violatingReportID = getViolatingReportIDForLHN(report, transactionViolations);
+ if (violatingReportID) {
+ const reportPreviewAction = Object.values(reportActions ?? {}).find((action) => isReportPreviewAction(action) && getOriginalMessage(action)?.linkedReportID === violatingReportID);
return {
reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS,
+ reportAction: reportPreviewAction,
};
}
@@ -792,6 +797,7 @@ function getOptionData({
result.pendingAction = report.pendingFields?.addWorkspaceRoom ?? report.pendingFields?.createChat;
result.brickRoadIndicator = reportAttributes?.brickRoadStatus;
result.actionBadge = reportAttributes?.actionBadge;
+ result.actionBadgeReportActionID = reportAttributes?.actionBadgeReportActionID;
result.ownerAccountID = report.ownerAccountID;
result.managerID = report.managerID;
result.reportID = report.reportID;
diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts
index 2360aff7e04b3..dfa13460b006f 100644
--- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts
+++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts
@@ -209,6 +209,7 @@ export default createOnyxDerivedValueConfig({
reportErrors,
oneTransactionThreadReportID,
actionBadge: actionGreenBadge,
+ actionBadgeReportActionID: greenBadgeReportActionID,
} = generateReportAttributes({
report,
chatReport,
@@ -219,15 +220,28 @@ export default createOnyxDerivedValueConfig({
let brickRoadStatus;
let actionBadge;
+ let actionBadgeReportActionID;
// if report has errors or violations, show red dot
- if (SidebarUtils.shouldShowRedBrickRoad(report, chatReport, reportActionsList, hasAnyViolations, reportErrors, transactions, transactionViolations, !!isReportArchived)) {
+ const reasonAndReportAction = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
+ report,
+ chatReport,
+ reportActionsList,
+ hasAnyViolations,
+ reportErrors,
+ transactions,
+ transactionViolations,
+ !!isReportArchived,
+ );
+ if (reasonAndReportAction) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
actionBadge = CONST.REPORT.ACTION_BADGE.FIX;
+ actionBadgeReportActionID = reasonAndReportAction.reportAction?.reportActionID;
}
// if report does not have error, check if it should show green dot
if (brickRoadStatus !== CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && requiresAttention) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
actionBadge = actionGreenBadge;
+ actionBadgeReportActionID = greenBadgeReportActionID;
}
acc[report.reportID] = {
@@ -249,6 +263,7 @@ export default createOnyxDerivedValueConfig({
brickRoadStatus,
requiresAttention,
actionBadge,
+ actionBadgeReportActionID,
reportErrors,
oneTransactionThreadReportID,
};
diff --git a/src/pages/inbox/sidebar/SidebarLinks.tsx b/src/pages/inbox/sidebar/SidebarLinks.tsx
index 4c1bdc22ff6be..d958b7761774e 100644
--- a/src/pages/inbox/sidebar/SidebarLinks.tsx
+++ b/src/pages/inbox/sidebar/SidebarLinks.tsx
@@ -16,6 +16,7 @@ import {cancelSpan} from '@libs/telemetry/activeSpans';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import * as ReportActionContextMenu from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
import CONST from '@src/CONST';
+import type {OptionData} from '@src/libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Report} from '@src/types/onyx';
@@ -51,7 +52,7 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO
* Show Report page with selected report id
*/
const showReportPage = useCallback(
- (option: Report) => {
+ (option: OptionData) => {
// Prevent opening Report page when clicking LHN row quickly after clicking FAB icon
// or when clicking the active LHN row on large screens
// or when continuously clicking different LHNs, only apply to small screen
@@ -70,7 +71,7 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO
cancelSpan(`${CONST.TELEMETRY.SPAN_OPEN_REPORT}_${option.reportID}`);
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID, option.actionBadgeReportActionID));
},
[shouldUseNarrowLayout, isActiveReport],
);
diff --git a/src/types/onyx/DerivedValues.ts b/src/types/onyx/DerivedValues.ts
index 379cf0ce8a336..82e883b58f289 100644
--- a/src/types/onyx/DerivedValues.ts
+++ b/src/types/onyx/DerivedValues.ts
@@ -33,6 +33,10 @@ type ReportAttributes = {
* The action badge to display instead of the GBR/RBR dot (e.g. 'submit', 'approve', 'pay').
*/
actionBadge?: ValueOf;
+ /**
+ * The reportActionID associated with the action badge, used for deep-linking from LHN.
+ */
+ actionBadgeReportActionID?: string;
/**
* The errors of the report.
*/