diff --git a/src/components/TestDrive/Modal/EmployeeTestDriveModal.tsx b/src/components/TestDrive/Modal/EmployeeTestDriveModal.tsx index d12f46d459d0b..ce29cce99cb73 100644 --- a/src/components/TestDrive/Modal/EmployeeTestDriveModal.tsx +++ b/src/components/TestDrive/Modal/EmployeeTestDriveModal.tsx @@ -1,4 +1,5 @@ import {useRoute} from '@react-navigation/native'; +import {validTransactionDraftIDsSelector} from '@selectors/TransactionDraft'; import {format} from 'date-fns'; import {Str} from 'expensify-common'; import React, {useCallback, useMemo, useState} from 'react'; @@ -49,8 +50,8 @@ function EmployeeTestDriveModal() { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const personalPolicy = usePersonalPolicy(); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const [draftTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT); const hasOnlyPersonalPolicies = useMemo(() => hasOnlyPersonalPoliciesUtil(allPolicies), [allPolicies]); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const onBossEmailChange = useCallback((value: string) => { setBossEmail(value); @@ -86,7 +87,7 @@ function EmployeeTestDriveModal() { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, }); setMoneyRequestReceipt(transactionID, source, filename, true, CONST.TEST_RECEIPT.FILE_TYPE, false, true); diff --git a/src/hooks/useReceiptScanDrop.tsx b/src/hooks/useReceiptScanDrop.tsx index 71c9f09a7de6e..5746bea6407be 100644 --- a/src/hooks/useReceiptScanDrop.tsx +++ b/src/hooks/useReceiptScanDrop.tsx @@ -1,3 +1,4 @@ +import {validTransactionDraftIDsSelector} from '@selectors/TransactionDraft'; import React, {useMemo} from 'react'; import {setTransactionReport} from '@libs/actions/Transaction'; import {navigateToParticipantPage} from '@libs/IOUUtils'; @@ -35,7 +36,7 @@ function useReceiptScanDrop() { const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); const [personalPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${personalPolicyID}`); - const [draftTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); // Memoize the new report ID to avoid re-generating it on every render and cause the hook to change, which leads to performance issues. const newReportID = useMemo(() => generateReportID(), []); @@ -52,7 +53,7 @@ function useReceiptScanDrop() { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, }); const newReceiptFiles: ReceiptFile[] = []; diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 47d72fd6e80e0..e6ffd15298730 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -297,7 +297,7 @@ type InitMoneyRequestParams = { lastSelectedDistanceRates?: OnyxEntry; currentUserPersonalDetails: CurrentUserPersonalDetails; hasOnlyPersonalPolicies: boolean; - draftTransactions: OnyxCollection; + draftTransactionIDs?: string[]; }; type MoneyRequestInformation = { @@ -1262,7 +1262,7 @@ function initMoneyRequest({ lastSelectedDistanceRates, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, }: InitMoneyRequestParams) { // Generate a brand new transactionID const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID; @@ -1271,7 +1271,12 @@ function initMoneyRequest({ const created = currentDate ?? format(new Date(), 'yyyy-MM-dd'); // We remove draft transactions created during multi scanning if there are some - removeDraftTransactions(true, draftTransactions); + if (draftTransactionIDs) { + const idsToRemove = draftTransactionIDs.filter((id) => id !== CONST.IOU.OPTIMISTIC_TRANSACTION_ID); + removeDraftTransactionsByIDs(idsToRemove); + } else { + removeDraftTransactions(true); + } // in case we have to re-init money request, but the IOU request type is the same with the old draft transaction, // we should keep most of the existing data by using the ONYX MERGE operation diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index 02a178f3e86ef..c19904f6894e9 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -91,8 +91,6 @@ function SubmitDetailsPage({ const fileName = shouldUsePreValidatedFile ? getFileName(validFilesToUpload?.uri ?? CONST.ATTACHMENT_IMAGE_DEFAULT_NAME) : getFileName(currentAttachment?.content ?? ''); const fileType = shouldUsePreValidatedFile ? (validFilesToUpload?.type ?? CONST.RECEIPT_ALLOWED_FILE_TYPES.JPEG) : (currentAttachment?.mimeType ?? ''); const [hasOnlyPersonalPolicies = false] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: hasOnlyPersonalPoliciesUtil}); - const [draftTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT); - useEffect(() => { if (!errorTitle || !errorMessage) { return; @@ -113,9 +111,9 @@ function SubmitDetailsPage({ currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, }); - // The draftTransactions can be changed if users update the expense, so we don't want to re-init the money request + // initMoneyRequest is an imported action, intentionally excluded to avoid re-initializing on every render // eslint-disable-next-line react-hooks/exhaustive-deps }, [reportOrAccountID, policy, personalPolicy, report, parentReport, currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies]); diff --git a/src/pages/inbox/report/ReportActionCompose/useAttachmentUploadValidation.ts b/src/pages/inbox/report/ReportActionCompose/useAttachmentUploadValidation.ts index b46023e583157..ce7198781020a 100644 --- a/src/pages/inbox/report/ReportActionCompose/useAttachmentUploadValidation.ts +++ b/src/pages/inbox/report/ReportActionCompose/useAttachmentUploadValidation.ts @@ -1,3 +1,4 @@ +import {validTransactionDraftIDsSelector} from '@selectors/TransactionDraft'; import {useCallback, useContext, useMemo, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import useFilesValidation from '@hooks/useFilesValidation'; @@ -56,9 +57,9 @@ function useAttachmentUploadValidation({ const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const personalPolicy = usePersonalPolicy(); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const [draftTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT); const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const hasOnlyPersonalPolicies = useMemo(() => hasOnlyPersonalPoliciesUtil(allPolicies), [allPolicies]); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const reportAttachmentsContext = useContext(AttachmentModalContext); const showAttachmentModalScreen = useCallback( @@ -104,7 +105,7 @@ function useAttachmentUploadValidation({ currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, }); for (const [index, file] of files.entries()) { diff --git a/src/pages/iou/request/DistanceRequestStartPage.tsx b/src/pages/iou/request/DistanceRequestStartPage.tsx index 2ff4962c1786c..7ca4b5f0bd070 100644 --- a/src/pages/iou/request/DistanceRequestStartPage.tsx +++ b/src/pages/iou/request/DistanceRequestStartPage.tsx @@ -1,4 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; +import {validTransactionDraftIDsSelector} from '@selectors/TransactionDraft'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {Keyboard, View} from 'react-native'; import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement'; @@ -57,7 +58,6 @@ function DistanceRequestStartPage({ const [lastDistanceExpenseType] = useOnyx(ONYXKEYS.NVP_LAST_DISTANCE_EXPENSE_TYPE); const isLoadingSelectedTab = isLoadingOnyxValue(selectedTabResult); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${getNonEmptyStringOnyxID(route?.params.transactionID)}`); - const [draftTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); const [currentDate] = useOnyx(ONYXKEYS.CURRENT_DATE); @@ -67,6 +67,7 @@ function DistanceRequestStartPage({ const hasOnlyPersonalPolicies = useMemo(() => hasOnlyPersonalPoliciesUtil(allPolicies), [allPolicies]); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const personalPolicy = usePersonalPolicy(); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const tabTitles = { [CONST.IOU.TYPE.REQUEST]: translate('iou.trackDistance'), @@ -120,7 +121,7 @@ function DistanceRequestStartPage({ lastSelectedDistanceRates, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, }); }, [ @@ -137,7 +138,7 @@ function DistanceRequestStartPage({ lastSelectedDistanceRates, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, ], ); diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 21b5278b84d83..cf95dce37f96d 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -1,5 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import {iouRequestPolicyCollectionSelector} from '@selectors/Policy'; +import {validTransactionDraftIDsSelector} from '@selectors/TransactionDraft'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {Keyboard, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -46,7 +47,7 @@ import type SCREENS from '@src/SCREENS'; import type {DismissedProductTraining, SelectedTabRequest} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -import IOURequestStepAmount from './step/IOURequestStepAmount'; +import {IOURequestStepAmountWithTransactionOnly} from './step/IOURequestStepAmount'; import IOURequestStepDestination from './step/IOURequestStepDestination'; import IOURequestStepDistance from './step/IOURequestStepDistance'; import IOURequestStepHours from './step/IOURequestStepHours'; @@ -81,6 +82,7 @@ function IOURequestStartPage({ const shouldUseTab = iouType !== CONST.IOU.TYPE.SEND && iouType !== CONST.IOU.TYPE.PAY && iouType !== CONST.IOU.TYPE.INVOICE; const personalPolicy = usePersonalPolicy(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); + const [reportDraft] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`); const policy = usePolicy(report?.policyID); const [lastSelectedTab, selectedTabResult] = useOnyx(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.IOU_REQUEST_TYPE}`); @@ -95,11 +97,11 @@ function IOURequestStartPage({ }); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); - const [draftTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT); const [isMultiScanEnabled, setIsMultiScanEnabled] = useState(false); const [currentDate] = useOnyx(ONYXKEYS.CURRENT_DATE); const {isOffline} = useNetwork(); const [hasUserSubmittedExpenseOrScannedReceipt] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {selector: isTestReceiptTooltipDismissedSelector}); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const hasOnlyPersonalPolicies = useMemo(() => hasOnlyPersonalPoliciesUtil(allPolicies), [allPolicies]); const perDiemInputRef = useRef(null); @@ -199,7 +201,7 @@ function IOURequestStartPage({ lastSelectedDistanceRates, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, }); }, [ @@ -216,7 +218,7 @@ function IOURequestStartPage({ lastSelectedDistanceRates, currentUserPersonalDetails, hasOnlyPersonalPolicies, - draftTransactions, + draftTransactionIDs, ], ); @@ -324,10 +326,12 @@ function IOURequestStartPage({ {() => ( - )} @@ -408,10 +412,12 @@ function IOURequestStartPage({ onContainerElementChanged={setActiveTabContainerElement} style={[styles.flexColumn, styles.flex1]} > - )} diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 8831be3e1ed13..39e7b952b69cb 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -108,10 +108,10 @@ function IOURequestStepAmount({ const defaultExpensePolicy = useDefaultExpensePolicy(); const personalPolicy = usePersonalPolicy(); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); - const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transactionID ? [transactionID] : []); + const isEditing = action === CONST.IOU.ACTION.EDIT; + const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(isEditing && transactionID ? [transactionID] : []); const reportAttributesDerived = useReportAttributes(); const privateIsArchivedMap = usePrivateIsArchivedMap(); - const isEditing = action === CONST.IOU.ACTION.EDIT; const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isCreateAction = action === CONST.IOU.ACTION.CREATE; const isSubmitAction = action === CONST.IOU.ACTION.SUBMIT; @@ -130,9 +130,9 @@ function IOURequestStepAmount({ const isUnreportedDistanceExpense = isEditing && isDistanceRequest(transaction) && isExpenseUnreported(transaction); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [transactionDrafts] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftsSelector}); const draftTransactionIDs = Object.keys(transactionDrafts ?? {}); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const currentUserAccountIDParam = currentUserPersonalDetails.accountID; const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; @@ -491,5 +491,8 @@ const IOURequestStepAmountWithWritableReportOrNotFound = withWritableReportOrNot // eslint-disable-next-line rulesdir/no-negated-variables const IOURequestStepAmountWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepAmountWithWritableReportOrNotFound, true); +// Version without withWritableReportOrNotFound, for use when parent already provides report prop +const IOURequestStepAmountWithTransactionOnly = withFullTransactionOrNotFound(IOURequestStepAmount, true); + export default IOURequestStepAmountWithFullTransactionOrNotFound; -export {isParticipantP2P}; +export {isParticipantP2P, IOURequestStepAmountWithTransactionOnly}; diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 816f807addbc0..56b4003cb3cf3 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -11616,7 +11616,6 @@ describe('actions/IOU', () => { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies: false, - draftTransactions: undefined, }); }) .then(async () => { @@ -11639,7 +11638,6 @@ describe('actions/IOU', () => { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies: false, - draftTransactions: undefined, }); }) .then(async () => { @@ -11663,7 +11661,6 @@ describe('actions/IOU', () => { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies: false, - draftTransactions: undefined, }); }) .then(async () => { @@ -11684,10 +11681,6 @@ describe('actions/IOU', () => { // Set up an additional draft transaction await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${otherDraftTransactionID}`, otherDraftTransaction); - const draftTransactions = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${otherDraftTransactionID}`]: otherDraftTransaction, - }; - await waitForBatchedUpdates() .then(() => { initMoneyRequest({ @@ -11701,7 +11694,6 @@ describe('actions/IOU', () => { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies: false, - draftTransactions, }); }) .then(async () => { @@ -11727,11 +11719,6 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${otherDraftTransactionID}`, otherDraftTransaction); await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`, existingOptimisticTransaction); - const draftTransactions = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${otherDraftTransactionID}`]: otherDraftTransaction, - [`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`]: existingOptimisticTransaction, - }; - await waitForBatchedUpdates() .then(() => { initMoneyRequest({ @@ -11745,7 +11732,6 @@ describe('actions/IOU', () => { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies: false, - draftTransactions, }); }) .then(async () => { @@ -11772,11 +11758,6 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${draftTransactionID1}`, draftTransaction1); await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${draftTransactionID2}`, draftTransaction2); - const draftTransactions = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${draftTransactionID1}`]: draftTransaction1, - [`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${draftTransactionID2}`]: draftTransaction2, - }; - await waitForBatchedUpdates() .then(() => { initMoneyRequest({ @@ -11790,7 +11771,6 @@ describe('actions/IOU', () => { currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies: false, - draftTransactions, }); }) .then(async () => {