diff --git a/src/libs/API/parameters/DismissExportErrorParams.ts b/src/libs/API/parameters/DismissExportErrorParams.ts new file mode 100644 index 000000000000..feee72133d59 --- /dev/null +++ b/src/libs/API/parameters/DismissExportErrorParams.ts @@ -0,0 +1,6 @@ +type DismissExportErrorParams = { + reportID: string; + reportActionID: string; +}; + +export default DismissExportErrorParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index bece054a0237..6eea3e538e5d 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -272,6 +272,7 @@ export type {default as ShareTrackedExpenseParams} from './ShareTrackedExpensePa export type {default as CategorizeTrackedExpenseParams} from './CategorizeTrackedExpenseParams'; export type {default as LeavePolicyParams} from './LeavePolicyParams'; export type {default as OpenPolicyAccountingPageParams} from './OpenPolicyAccountingPageParams'; +export type {default as DismissExportErrorParams} from './DismissExportErrorParams'; export type {default as DismissViolationParams} from './DismissViolationParams'; export type {default as SearchParams} from './Search'; export type {default as SendInvoiceParams} from './SendInvoiceParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 3eb92f0892e4..0192f01d90d8 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -338,6 +338,7 @@ const WRITE_COMMANDS = { CATEGORIZE_TRACKED_EXPENSE: 'CategorizeTrackedExpense', SHARE_TRACKED_EXPENSE: 'ShareTrackedExpense', LEAVE_POLICY: 'LeavePolicy', + DISMISS_EXPORT_ERROR: 'DismissExportError', DISMISS_VIOLATION: 'DismissViolation', SEND_INVOICE: 'SendInvoice', PAY_INVOICE: 'PayInvoice', @@ -882,6 +883,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE]: Parameters.CategorizeTrackedExpenseParams; [WRITE_COMMANDS.SHARE_TRACKED_EXPENSE]: Parameters.ShareTrackedExpenseParams; [WRITE_COMMANDS.LEAVE_POLICY]: Parameters.LeavePolicyParams; + [WRITE_COMMANDS.DISMISS_EXPORT_ERROR]: Parameters.DismissExportErrorParams; [WRITE_COMMANDS.DISMISS_VIOLATION]: Parameters.DismissViolationParams; [WRITE_COMMANDS.SEND_INVOICE]: Parameters.SendInvoiceParams; [WRITE_COMMANDS.PAY_INVOICE]: Parameters.PayInvoiceParams; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index fe23be8b09d8..bb779c4df755 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,5 +1,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import * as API from '@libs/API'; +import {WRITE_COMMANDS} from '@libs/API/types'; import {getLinkedTransactionID, getReportAction, getReportActionMessage, isCreatedTaskReportAction} from '@libs/ReportActionsUtils'; import {getOriginalReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -105,8 +107,48 @@ function clearAllRelatedReportActionErrors(reportID: string | undefined, reportA } } +/** + * Dismiss an export error action (INTEGRATIONS_MESSAGE) from the chat. + * This removes the action from the UI and marks it as dismissed in the database. + * + * @param reportID - The report ID containing the action + * @param reportActionID - The report action ID to dismiss + */ +function dismissExportError(reportID: string, reportActionID: string) { + // Optimistically remove the action from the UI + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportActionID]: null, + }, + }, + ]; + + // If the request fails, restore the action (we'd need the original action data here) + // For now, we'll rely on the server to push the correct state + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [reportActionID]: { + errors: { + [Date.now()]: 'Failed to dismiss export error. Please try again.', + }, + }, + }, + }, + ]; + + const parameters = { + reportID, + reportActionID, + }; + + API.write(WRITE_COMMANDS.DISMISS_EXPORT_ERROR, parameters, {optimisticData, failureData}); +} + export type {IgnoreDirection}; -export { - // eslint-disable-next-line import/prefer-default-export - clearAllRelatedReportActionErrors, -}; +export {clearAllRelatedReportActionErrors, dismissExportError}; diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index f13fb59fe0f3..71416ca60760 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -54,6 +54,7 @@ import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import type {OnyxDataWithErrors} from '@libs/ErrorUtils'; import {getLatestErrorMessageField, isReceiptError} from '@libs/ErrorUtils'; +import {dismissExportError} from '@libs/actions/ReportActions'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import {isReportMessageAttachment} from '@libs/isReportMessageAttachment'; import Navigation from '@libs/Navigation/Navigation'; @@ -130,6 +131,7 @@ import { isCreatedTaskReportAction, isDeletedAction, isDeletedParentAction as isDeletedParentActionUtils, + isIntegrationMessageAction, isIOURequestReportAction, isMarkAsClosedAction, isMessageDeleted, @@ -559,6 +561,12 @@ function PureReportActionItem({ const [showConfirmDismissReceiptError, setShowConfirmDismissReceiptError] = useState(false); const dismissError = useCallback(() => { + // For INTEGRATIONS_MESSAGE actions (export errors), dismiss the entire action + if (isIntegrationMessageAction(action) && action.reportActionID) { + dismissExportError(reportID, action.reportActionID); + return; + } + const transactionID = isMoneyRequestAction(action) ? getOriginalMessage(action)?.IOUTransactionID : undefined; if (transactionID) { clearError(transactionID); diff --git a/src/pages/home/sidebar/SidebarLinks.tsx b/src/pages/home/sidebar/SidebarLinks.tsx index 29a016e7a19c..280fec5a1d85 100644 --- a/src/pages/home/sidebar/SidebarLinks.tsx +++ b/src/pages/home/sidebar/SidebarLinks.tsx @@ -5,14 +5,17 @@ import type {EdgeInsets} from 'react-native-safe-area-context'; import type {ValueOf} from 'type-fest'; import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; +import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {confirmReadyToOpenApp, setSidebarLoaded} from '@libs/actions/App'; import Navigation from '@libs/Navigation/Navigation'; +import {getAllReportActionsErrorsAndReportActionThatRequiresAttention} from '@libs/ReportUtils'; import {cancelSpan} from '@libs/telemetry/activeSpans'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; @@ -37,6 +40,7 @@ function SidebarLinks({insets, optionListItems, isLoading, priorityMode = CONST. const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: false}); useEffect(() => { confirmReadyToOpenApp(); @@ -56,23 +60,29 @@ function SidebarLinks({insets, optionListItems, isLoading, priorityMode = CONST. // or when clicking the active LHN row on large screens // or when continuously clicking different LHNs, only apply to small screen // since getTopmostReportId always returns on other devices - const reportActionID = Navigation.getTopmostReportActionId(); + const currentReportActionID = Navigation.getTopmostReportActionId(); // Prevent opening a new Report page if the user quickly taps on another conversation // before the first one is displayed. const shouldBlockReportNavigation = Navigation.getActiveRoute() !== '/home' && shouldUseNarrowLayout; if ( - (option.reportID === Navigation.getTopmostReportId() && !reportActionID) || - (shouldUseNarrowLayout && isActiveReport(option.reportID) && !reportActionID) || + (option.reportID === Navigation.getTopmostReportId() && !currentReportActionID) || + (shouldUseNarrowLayout && isActiveReport(option.reportID) && !currentReportActionID) || shouldBlockReportNavigation ) { cancelSpan(`${CONST.TELEMETRY.SPAN_OPEN_REPORT}_${option.reportID}`); return; } - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); + + // Check if this report has a report action with errors (RBR) and navigate to that action + const reportActionsForReport = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${option.reportID}`]; + const {reportAction: errorReportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(option, reportActionsForReport); + const errorReportActionID = errorReportAction?.reportActionID; + + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID, errorReportActionID)); }, - [shouldUseNarrowLayout, isActiveReport], + [shouldUseNarrowLayout, isActiveReport, reportActions], ); const viewMode = priorityMode === CONST.PRIORITY_MODE.GSD ? CONST.OPTION_MODE.COMPACT : CONST.OPTION_MODE.DEFAULT;