diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8a20032b4f91..b5eea4228042 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -339,6 +339,9 @@ const ONYXKEYS = { /** Holds the checks used while transferring the ownership of the workspace */ POLICY_OWNERSHIP_CHANGE_CHECKS: 'policyOwnershipChangeChecks', + /** Stores info during review duplicates flow */ + REVIEW_DUPLICATES: 'reviewDuplicates', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -719,6 +722,7 @@ type OnyxValuesMapping = { [ONYXKEYS.CACHED_PDF_PATHS]: Record; [ONYXKEYS.POLICY_OWNERSHIP_CHANGE_CHECKS]: Record; [ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE]: OnyxTypes.QuickAction; + [ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates; [ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_BILLING_FUND_ID]: number; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c1fdd68951fa..f39e4b5aa431 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -823,6 +823,11 @@ const ROUTES = { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const, }, + TRANSACTION_DUPLICATE_REVIEW_PAGE: { + route: 'r/:threadReportID/duplicates/review', + getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review` as const, + }, + POLICY_ACCOUNTING_XERO_IMPORT: { route: 'settings/workspaces/:policyID/accounting/xero/import', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index f884cca94ef5..f89469873ac7 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -138,6 +138,7 @@ const SCREENS = { ROOM_INVITE: 'RoomInvite', REFERRAL: 'Referral', PROCESS_MONEY_REQUEST_HOLD: 'ProcessMoneyRequestHold', + TRANSACTION_DUPLICATE: 'TransactionDuplicate', TRAVEL: 'Travel', SEARCH_REPORT: 'SearchReport', SETTINGS_CATEGORIES: 'SettingsCategories', @@ -179,6 +180,10 @@ const SCREENS = { STATE_SELECTOR: 'Money_Request_State_Selector', }, + TRANSACTION_DUPLICATE: { + REVIEW: 'Transaction_Duplicate_Review', + }, + IOU_SEND: { ADD_BANK_ACCOUNT: 'IOU_Send_Add_Bank_Account', ADD_DEBIT_CARD: 'IOU_Send_Add_Debit_Card', diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index b10a09e4f7e8..82b33a674b18 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -80,7 +80,6 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']); const shouldShowMarkAsCashButton = isDraft && hasAllPendingRTERViolations; - const deleteTransaction = useCallback(() => { if (parentReportAction) { const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? '-1' : '-1'; @@ -245,6 +244,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow medium text={translate('iou.reviewDuplicates')} style={[styles.p0]} + onPress={() => { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(report.reportID)); + }} /> )} @@ -266,6 +268,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow medium text={translate('iou.reviewDuplicates')} style={[styles.w100, styles.pr0]} + onPress={() => { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(report.reportID)); + }} /> )} diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index b8f02f83d1cd..15f9cee3705c 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -59,6 +59,9 @@ type MoneyRequestActionProps = MoneyRequestActionOnyxProps & { /** Styles to be assigned to Container */ style?: StyleProp; + + /** Whether context menu should be shown on press */ + shouldDisplayContextMenu?: boolean; }; function MoneyRequestAction({ @@ -75,11 +78,11 @@ function MoneyRequestAction({ isHovered = false, style, isWhisper = false, + shouldDisplayContextMenu = true, }: MoneyRequestActionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const isSplitBillAction = ReportActionsUtils.isSplitBillAction(action); const isTrackExpenseAction = ReportActionsUtils.isTrackExpenseAction(action); @@ -132,6 +135,7 @@ function MoneyRequestAction({ containerStyles={[styles.cursorPointer, isHovered ? styles.reportPreviewBoxHoverBorder : undefined, style]} isHovered={isHovered} isWhisper={isWhisper} + shouldDisplayContextMenu={shouldDisplayContextMenu} /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 952e66cb1154..05df4acac5a6 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -1,8 +1,10 @@ +import {useRoute} from '@react-navigation/native'; import lodashSortBy from 'lodash/sortBy'; import truncate from 'lodash/truncate'; import React, {useMemo} from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; +import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {ReceiptScan} from '@components/Icon/Expensicons'; @@ -35,7 +37,10 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; +import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -60,12 +65,14 @@ function MoneyRequestPreviewContent({ isHovered = false, isWhisper = false, transactionViolations, + shouldDisplayContextMenu = true, }: MoneyRequestPreviewProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {windowWidth} = useWindowDimensions(); + const route = useRoute(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const sessionAccountID = session?.accountID; @@ -111,12 +118,23 @@ function MoneyRequestPreviewContent({ const isCardTransaction = TransactionUtils.isCardTransaction(transaction); const isSettled = ReportUtils.isSettled(iouReport?.reportID); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + const isReviewDuplicateTransactionPage = route.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW; + const isFullySettled = isSettled && !isSettlementOrApprovalPartial; const isFullyApproved = ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial; const shouldShowRBR = hasNoticeTypeViolations || hasViolations || hasFieldErrors || (!isFullySettled && !isFullyApproved && isOnHold); const showCashOrCard = isCardTransaction ? translate('iou.card') : translate('iou.cash'); const shouldShowHoldMessage = !(isSettled && !isSettlementOrApprovalPartial) && isOnHold; + // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates + const duplicates = useMemo( + () => + transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]?.find( + (violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, + )?.data?.duplicates ?? [], + [transaction?.transactionID, transactionViolations], + ); + /* Show the merchant for IOUs and expenses only if: - the merchant is not empty, is custom, or is not related to scanning smartscan; @@ -145,6 +163,9 @@ function MoneyRequestPreviewContent({ }; const showContextMenu = (event: GestureResponderEvent) => { + if (!shouldDisplayContextMenu) { + return; + } showContextMenuForReport(event, contextMenuAnchor, reportID, action, checkIfContextMenuActive); }; @@ -382,6 +403,17 @@ function MoneyRequestPreviewContent({ ]} > {childContainer} + {isReviewDuplicateTransactionPage && ( +