diff --git a/src/components/ReportActionItem/ExportWithDropdownMenu.tsx b/src/components/ReportActionItem/ExportWithDropdownMenu.tsx index a13c0a266689..87ddb3b42bf0 100644 --- a/src/components/ReportActionItem/ExportWithDropdownMenu.tsx +++ b/src/components/ReportActionItem/ExportWithDropdownMenu.tsx @@ -74,7 +74,7 @@ function ExportWithDropdownMenu({policy, report, connectionName}: ExportWithDrop if (modalStatus === CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION) { ReportActions.exportToIntegration(reportID, connectionName); } else if (modalStatus === CONST.REPORT.EXPORT_OPTIONS.MARK_AS_EXPORTED) { - ReportActions.markAsManuallyExported(reportID); + ReportActions.markAsManuallyExported(reportID, connectionName); } }, [connectionName, modalStatus, reportID]); @@ -106,7 +106,7 @@ function ExportWithDropdownMenu({policy, report, connectionName}: ExportWithDrop if (value === CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION) { ReportActions.exportToIntegration(reportID, connectionName); } else if (value === CONST.REPORT.EXPORT_OPTIONS.MARK_AS_EXPORTED) { - ReportActions.markAsManuallyExported(reportID); + ReportActions.markAsManuallyExported(reportID, connectionName); } }} onOptionSelected={({value}) => savePreferredExportMethod(value)} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 7a527610422b..68f5bca3d764 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -152,7 +152,7 @@ function ReportPreview({ const hasReceipts = transactionsWithReceipts.length > 0; const isScanning = hasReceipts && areAllRequestsBeingSmartScanned; const hasErrors = - hasMissingSmartscanFields || + (hasMissingSmartscanFields && !iouSettled) || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (canUseViolations && (ReportUtils.hasViolations(iouReportID, transactionViolations) || ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations))) || (ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) || @@ -285,7 +285,7 @@ function ReportPreview({ const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage; const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID); - const shouldShowRBR = !iouSettled && hasErrors; + const shouldShowRBR = hasErrors; /* Show subtitle if at least one of the expenses is not being smart scanned, and either: @@ -425,7 +425,7 @@ function ReportPreview({ )} - {shouldShowSettlementButton && !shouldShowExportIntegrationButton && ( + {shouldShowSettlementButton && ( )} - {shouldShowExportIntegrationButton && ( + {shouldShowExportIntegrationButton && !shouldShowSettlementButton && ( + */ + data: string; }; export default MarkAsExportedParams; diff --git a/src/libs/API/parameters/ReportExportParams.ts b/src/libs/API/parameters/ReportExportParams.ts index dc87ce2170c4..c6a4b7b58ee8 100644 --- a/src/libs/API/parameters/ReportExportParams.ts +++ b/src/libs/API/parameters/ReportExportParams.ts @@ -5,6 +5,13 @@ type ReportExportParams = { reportIDList: string; connectionName: ValueOf; type: 'MANUAL'; + /** + * Stringified JSON object with type of following structure: + * { + * [reportID]: optimisticReportActionID; + * }> + */ + optimisticReportActions: string; }; export default ReportExportParams; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9194ce15ef42..d56feca5ca0e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -43,6 +43,7 @@ import type { UserWallet, } from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; +import type {OriginalMessageExportedToIntegration} from '@src/types/onyx/OldDotAction'; import type Onboarding from '@src/types/onyx/Onboarding'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import type {OriginalMessageChangeLog, PaymentMethodType} from '@src/types/onyx/OriginalMessage'; @@ -287,6 +288,12 @@ type OptimisticChatReport = Pick< isOptimisticReport: true; }; +type OptimisticExportIntegrationAction = OriginalMessageExportedToIntegration & + Pick< + ReportAction, + 'reportActionID' | 'actorAccountID' | 'avatar' | 'created' | 'lastModified' | 'message' | 'person' | 'shouldShow' | 'pendingAction' | 'errors' | 'automatic' + >; + type OptimisticTaskReportAction = Pick< ReportAction, | 'reportActionID' @@ -5184,6 +5191,40 @@ function buildOptimisticTaskReport( }; } +/** + * Builds an optimistic EXPORTED_TO_INTEGRATION report action + * + * @param integration - The connectionName of the integration + * @param markedManually - Whether the integration was marked as manually exported + */ +function buildOptimisticExportIntegrationAction(integration: ConnectionName, markedManually = false): OptimisticExportIntegrationAction { + const label = CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integration]; + return { + reportActionID: NumberUtils.rand64(), + actionName: CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + actorAccountID: currentUserAccountID, + message: [], + person: [ + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: getCurrentUserDisplayNameOrEmail(), + }, + ], + automatic: false, + avatar: getCurrentUserAvatar(), + created: DateUtils.getDBTime(), + shouldShow: true, + originalMessage: { + label, + lastModified: DateUtils.getDBTime(), + markedManually, + inProgress: true, + }, + }; +} + /** * A helper method to create transaction thread * @@ -7528,6 +7569,7 @@ export { isAdminOwnerApproverOrReportOwner, createDraftWorkspaceAndNavigateToConfirmationScreen, isChatUsedForOnboarding, + buildOptimisticExportIntegrationAction, getChatUsedForOnboarding, getFieldViolationTranslation, getFieldViolation, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index b4d1360b2d94..9cb92ebd7c8a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -25,6 +25,7 @@ import type { InviteToGroupChatParams, InviteToRoomParams, LeaveRoomParams, + MarkAsExportedParams, MarkAsUnreadParams, OpenReportParams, OpenRoomMembersPageParams, @@ -32,6 +33,7 @@ import type { RemoveEmojiReactionParams, RemoveFromGroupChatParams, RemoveFromRoomParams, + ReportExportParams, ResolveActionableMentionWhisperParams, ResolveActionableReportMentionWhisperParams, SearchForReportsParams, @@ -3835,18 +3837,94 @@ function setGroupDraft(newGroupDraft: Partial) { } function exportToIntegration(reportID: string, connectionName: ConnectionName) { - API.write(WRITE_COMMANDS.REPORT_EXPORT, { + const action = ReportUtils.buildOptimisticExportIntegrationAction(connectionName); + const optimisticReportActionID = action.reportActionID; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticReportActionID]: action, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticReportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + }, + ]; + + const params = { reportIDList: reportID, connectionName, type: 'MANUAL', - }); + optimisticReportActions: JSON.stringify({ + [reportID]: optimisticReportActionID, + }), + } satisfies ReportExportParams; + + API.write(WRITE_COMMANDS.REPORT_EXPORT, params, {optimisticData, failureData}); } -function markAsManuallyExported(reportID: string) { - API.write(WRITE_COMMANDS.MARK_AS_EXPORTED, { - reportIDList: reportID, +function markAsManuallyExported(reportID: string, connectionName: ConnectionName) { + const action = ReportUtils.buildOptimisticExportIntegrationAction(connectionName, true); + const label = CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]; + const optimisticReportActionID = action.reportActionID; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticReportActionID]: action, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticReportActionID]: { + pendingAction: null, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticReportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + }, + ]; + + const params = { markedManually: true, - }); + data: JSON.stringify([ + { + reportID, + label, + optimisticReportActionID, + }, + ]), + } satisfies MarkAsExportedParams; + + API.write(WRITE_COMMANDS.MARK_AS_EXPORTED, params, {optimisticData, successData, failureData}); } export { diff --git a/src/pages/home/report/ReportDetailsExportPage.tsx b/src/pages/home/report/ReportDetailsExportPage.tsx index 99b7305cc7a9..6f16786682af 100644 --- a/src/pages/home/report/ReportDetailsExportPage.tsx +++ b/src/pages/home/report/ReportDetailsExportPage.tsx @@ -45,7 +45,7 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { if (type === CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION) { ReportActions.exportToIntegration(reportID, connectionName); } else if (type === CONST.REPORT.EXPORT_OPTIONS.MARK_AS_EXPORTED) { - ReportActions.markAsManuallyExported(reportID); + ReportActions.markAsManuallyExported(reportID, connectionName); } setModalStatus(null); Navigation.dismissModal();