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. */