From 913a3d95b71cbb341946f38f04e1fc2d0cef715e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 21 Aug 2024 22:13:58 +0530 Subject: [PATCH 001/235] demo skip dupe tax step. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 25 ++++++++++++------- .../TransactionDuplicate/ReviewTaxCode.tsx | 9 ++++--- .../step/IOURequestStepConfirmation.tsx | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8597654576fc..02a3764bf419 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -31,6 +31,8 @@ import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import {getPolicy} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -279,15 +281,20 @@ function MoneyRequestPreviewContent({ const navigateToReviewFields = () => { const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID); Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? ''}); - if ('merchant' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); - } else if ('category' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); - } else if ('tag' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); - } else if ('description' in comparisonResult.change) { - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); - } else if ('taxCode' in comparisonResult.change) { + + const report2 = ReportUtils.getReport(transaction?.reportID ?? ''); + const policy = PolicyUtils.getPolicy(report2?.policyID); + const hasValidTaxes = comparisonResult.change.taxCode?.filter((taxID) => PolicyUtils.getTaxByID(policy, taxID ?? '')?.name).length; + // if ('merchant' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); + // } else if ('category' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); + // } else if ('tag' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); + // } else if ('description' in comparisonResult.change) { + // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); + // } + if ('taxCode' in comparisonResult.change && hasValidTaxes) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index aa598bff8fcd..acb45b2c8a63 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -20,13 +20,13 @@ import ReviewFields from './ReviewFields'; function ReviewTaxRate() { const route = useRoute>(); const {translate} = useLocalize(); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const transaction = TransactionUtils.getTransaction(reviewDuplicates?.transactionID ?? ''); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(reviewDuplicates?.transactionID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'taxCode', route.params.threadReportID ?? ''); - const transaction = TransactionUtils.getTransaction(transactionID); const options = useMemo( () => compareResult.change.taxCode?.map((taxID) => @@ -39,6 +39,7 @@ function ReviewTaxRate() { ), [compareResult.change.taxCode, policy, transaction, translate], ); + const getTaxAmount = useCallback( (taxID: string) => { const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxID); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index b33ce6f56600..5090c1dfb2d6 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; From 0429b7f0e996e11f6649f218908d38add53c8e1f Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 28 Aug 2024 09:02:26 +0530 Subject: [PATCH 002/235] minor update. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index d901e3eb7c3a..9f2f52d7ac35 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -294,6 +294,10 @@ function MoneyRequestPreviewContent({ // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); // } if ('taxCode' in comparisonResult.change && hasValidTaxes) { + if (!hasValidTaxes) { + Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); + return; + } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); From 90508101c480f9b346da03d06c291394d28532b4 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 11 Sep 2024 11:38:15 +0530 Subject: [PATCH 003/235] revert changes in ReviewTaxRate page. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 28 ++++++++++--------- .../TransactionDuplicate/ReviewTaxCode.tsx | 9 +++--- .../step/IOURequestStepConfirmation.tsx | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index ecd5b4e73548..89a1c8dc412b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -291,20 +291,22 @@ function MoneyRequestPreviewContent({ const report2 = ReportUtils.getReport(transaction?.reportID ?? ''); const policy = PolicyUtils.getPolicy(report2?.policyID); const hasValidTaxes = comparisonResult.change.taxCode?.filter((taxID) => PolicyUtils.getTaxByID(policy, taxID ?? '')?.name).length; - // if ('merchant' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); - // } else if ('category' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); - // } else if ('tag' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); - // } else if ('description' in comparisonResult.change) { - // Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); - // } + + if (!hasValidTaxes) { + Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); + return; + } + + if ('merchant' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); + } else if ('category' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE.getRoute(route.params?.threadReportID)); + } else if ('tag' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE.getRoute(route.params?.threadReportID)); + } else if ('description' in comparisonResult.change) { + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); + } if ('taxCode' in comparisonResult.change && hasValidTaxes) { - if (!hasValidTaxes) { - Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); - return; - } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index acb45b2c8a63..aa598bff8fcd 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -20,13 +20,13 @@ import ReviewFields from './ReviewFields'; function ReviewTaxRate() { const route = useRoute>(); const {translate} = useLocalize(); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const transaction = TransactionUtils.getTransaction(reviewDuplicates?.transactionID ?? ''); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(reviewDuplicates?.transactionID ?? ''); + const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'taxCode', route.params.threadReportID ?? ''); + const transaction = TransactionUtils.getTransaction(transactionID); const options = useMemo( () => compareResult.change.taxCode?.map((taxID) => @@ -39,7 +39,6 @@ function ReviewTaxRate() { ), [compareResult.change.taxCode, policy, transaction, translate], ); - const getTaxAmount = useCallback( (taxID: string) => { const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxID); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 3f8f17d57d0f..6c1457abef62 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; From b2b979b31d4008e3b1580621c4f4542b8eef56ea Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 16 Sep 2024 02:54:28 +0530 Subject: [PATCH 004/235] revert changes. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index bf3c48e9e5b7..51f9c1c521a4 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -288,15 +288,6 @@ function MoneyRequestPreviewContent({ const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID); Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? ''}); - const report2 = ReportUtils.getReport(transaction?.reportID ?? ''); - const policy = PolicyUtils.getPolicy(report2?.policyID); - const hasValidTaxes = comparisonResult.change.taxCode?.filter((taxID) => PolicyUtils.getTaxByID(policy, taxID ?? '')?.name).length; - - if (!hasValidTaxes) { - Transaction.setReviewDuplicatesKey({taxCode: transaction?.taxCode}); - return; - } - if ('merchant' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); } else if ('category' in comparisonResult.change) { @@ -306,7 +297,7 @@ function MoneyRequestPreviewContent({ } else if ('description' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE.getRoute(route.params?.threadReportID)); } - if ('taxCode' in comparisonResult.change && hasValidTaxes) { + if ('taxCode' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE.getRoute(route.params?.threadReportID)); } else if ('billable' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE.getRoute(route.params?.threadReportID)); From 61ce0021e927f584d9aa4a8b1ed1afa11aa24d05 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 16 Sep 2024 03:27:01 +0530 Subject: [PATCH 005/235] remove tax code from changes if policy doesn't include that. Signed-off-by: krishna2323 --- .../MoneyRequestPreviewContent.tsx | 6 ++---- src/libs/TransactionUtils/index.ts | 11 +++++++++++ src/pages/TransactionDuplicate/ReviewBillable.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewCategory.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewDescription.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewMerchant.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewReimbursable.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewTag.tsx | 5 ++++- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 3 ++- src/types/onyx/ReviewDuplicates.ts | 3 +++ 10 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 51f9c1c521a4..8e1cfb4a29e8 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -31,8 +31,6 @@ import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import {getPolicy} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -285,8 +283,8 @@ function MoneyRequestPreviewContent({ ); const navigateToReviewFields = () => { - const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID); - Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? ''}); + const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID ?? ''); + Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? '', reportID: transaction?.reportID}); if ('merchant' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID)); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 63c2f9aa9862..a734995a60ea 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1020,6 +1020,17 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia } else { processChanges(fieldName, transactions, keys); } + } else if (fieldName === 'taxCode') { + const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const policy = PolicyUtils.getPolicy(report?.policyID); + const differentValues = getDifferentValues(transactions, keys); + const hasValidTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name).length; + + if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) || !hasValidTaxes) { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } else { + processChanges(fieldName, transactions, keys); + } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { diff --git a/src/pages/TransactionDuplicate/ReviewBillable.tsx b/src/pages/TransactionDuplicate/ReviewBillable.tsx index 4cb2b130aea1..cd9fa8e1ae92 100644 --- a/src/pages/TransactionDuplicate/ReviewBillable.tsx +++ b/src/pages/TransactionDuplicate/ReviewBillable.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewBillable() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'billable', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewCategory.tsx b/src/pages/TransactionDuplicate/ReviewCategory.tsx index 12a5968e2c43..04dbae092318 100644 --- a/src/pages/TransactionDuplicate/ReviewCategory.tsx +++ b/src/pages/TransactionDuplicate/ReviewCategory.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewCategory() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'category', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index e6229afe48ac..fee570273e05 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewDescription() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'description', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewMerchant.tsx b/src/pages/TransactionDuplicate/ReviewMerchant.tsx index 80ae43a0d338..5a94161a72d5 100644 --- a/src/pages/TransactionDuplicate/ReviewMerchant.tsx +++ b/src/pages/TransactionDuplicate/ReviewMerchant.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewMerchant() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'merchant', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx index fbf7e43a2013..43e3a063e106 100644 --- a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx +++ b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -16,7 +18,8 @@ function ReviewReimbursable() { const route = useRoute>(); const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'reimbursable', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewTag.tsx b/src/pages/TransactionDuplicate/ReviewTag.tsx index abab4d3e03f3..46c32b5f12ad 100644 --- a/src/pages/TransactionDuplicate/ReviewTag.tsx +++ b/src/pages/TransactionDuplicate/ReviewTag.tsx @@ -1,6 +1,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; import ReviewFields from './ReviewFields'; @@ -17,7 +19,8 @@ function ReviewTag() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'tag', route.params.threadReportID ?? ''); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index aa598bff8fcd..e52368f35005 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -23,7 +23,8 @@ function ReviewTaxRate() { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, navigateToNextScreen} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'taxCode', route.params.threadReportID ?? ''); const transaction = TransactionUtils.getTransaction(transactionID); diff --git a/src/types/onyx/ReviewDuplicates.ts b/src/types/onyx/ReviewDuplicates.ts index 0682ed0a7f7c..6c5ccbd93481 100644 --- a/src/types/onyx/ReviewDuplicates.ts +++ b/src/types/onyx/ReviewDuplicates.ts @@ -8,6 +8,9 @@ type ReviewDuplicates = { /** ID of transaction we want to keep */ transactionID: string; + /** ID of the transaction report we want to keep */ + reportID: string; + /** Merchant which user want to keep */ merchant: string; From 5fff73f2a953260d6e6719ea78d92ead9e4bfd98 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 16 Sep 2024 03:30:54 +0530 Subject: [PATCH 006/235] minor update. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index a734995a60ea..1d5fe405ee70 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -943,7 +943,7 @@ function removeSettledAndApprovedTransactions(transactionIDs: string[]) { * 6. It returns the 'keep' and 'change' objects. */ -function compareDuplicateTransactionFields(transactionID: string): {keep: Partial; change: FieldsToChange} { +function compareDuplicateTransactionFields(transactionID: string, reportID: string): {keep: Partial; change: FieldsToChange} { const transactionViolations = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; const duplicates = transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? []; const transactions = removeSettledAndApprovedTransactions([transactionID, ...duplicates]).map((item) => getTransaction(item)); From 9801ae89d3f0bf9ff300a2a4ed976b6ee9e2edad Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 17 Sep 2024 02:40:32 +0530 Subject: [PATCH 007/235] only add taxCode in changes if availble in policy. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 1d5fe405ee70..9fb78eea1555 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1024,12 +1024,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const policy = PolicyUtils.getPolicy(report?.policyID); const differentValues = getDifferentValues(transactions, keys); - const hasValidTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name).length; + const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); - if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) || !hasValidTaxes) { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; - } else { - processChanges(fieldName, transactions, keys); + if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && validTaxes.length > 1) { + change[fieldName] = validTaxes; } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; From b2b14cdc1033a168022dedca43baaa6b07518258 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 6 Oct 2024 15:37:21 +0530 Subject: [PATCH 008/235] feat: skip category review when category isn't present in policy. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 13 ++++++++++++- src/libs/actions/Policy/Category.ts | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 71f103999c91..401e29976f71 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -3,6 +3,7 @@ import lodashIsEqual from 'lodash/isEqual'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import {getPolicyCategories} from '@libs/actions/Policy/Category'; import type {TransactionMergeParams} from '@libs/API/parameters'; import {getCurrencyDecimals} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -1007,6 +1008,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const keys = fieldsToCompare[fieldName]; const firstTransaction = transactions.at(0); const isFirstTransactionCommentEmptyObject = typeof firstTransaction?.comment === 'object' && firstTransaction?.comment?.comment === ''; + const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); @@ -1023,7 +1025,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri processChanges(fieldName, transactions, keys); } } else if (fieldName === 'taxCode') { - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const policy = PolicyUtils.getPolicy(report?.policyID); const differentValues = getDifferentValues(transactions, keys); const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); @@ -1031,6 +1032,16 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && validTaxes.length > 1) { change[fieldName] = validTaxes; } + } else if (fieldName === 'category') { + const differentValues = getDifferentValues(transactions, keys); + const policyCategories = getPolicyCategories(report?.policyID ?? '-1'); + const availableCategories = Object.values(policyCategories) + .filter((category) => differentValues.includes(category.name)) + .map((e) => e.name); + + if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && availableCategories.length > 1) { + change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; + } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index e237ed80e293..2d4299cc224f 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1331,6 +1331,10 @@ function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: str API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX, parameters, onyxData); } +function getPolicyCategories(policyID: string) { + return allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {}; +} + export { openPolicyCategoriesPage, buildOptimisticPolicyRecentlyUsedCategories, @@ -1354,4 +1358,5 @@ export { setPolicyCategoryTax, importPolicyCategories, downloadCategoriesCSV, + getPolicyCategories, }; From 57bd4005b5c9982cb5e982ef4755da3b60002dda Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 6 Oct 2024 16:07:14 +0530 Subject: [PATCH 009/235] minor fix. Signed-off-by: krishna2323 --- src/pages/TransactionDuplicate/Confirmation.tsx | 2 +- src/pages/TransactionDuplicate/ReviewBillable.tsx | 2 +- src/pages/TransactionDuplicate/ReviewCategory.tsx | 2 +- src/pages/TransactionDuplicate/ReviewDescription.tsx | 2 +- src/pages/TransactionDuplicate/ReviewMerchant.tsx | 2 +- src/pages/TransactionDuplicate/ReviewReimbursable.tsx | 2 +- src/pages/TransactionDuplicate/ReviewTag.tsx | 2 +- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 15217e215ad4..a8d358184d77 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -38,7 +38,7 @@ function Confirmation() { const [reviewDuplicates, reviewDuplicatesResult] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); const transaction = useMemo(() => TransactionUtils.buildNewTransactionAfterReviewingDuplicates(reviewDuplicates), [reviewDuplicates]); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const {goBack} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'confirmation', route.params.threadReportID, route.params.backTo); const [report, reportResult] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`); diff --git a/src/pages/TransactionDuplicate/ReviewBillable.tsx b/src/pages/TransactionDuplicate/ReviewBillable.tsx index 9b9900741c2c..166c61209a42 100644 --- a/src/pages/TransactionDuplicate/ReviewBillable.tsx +++ b/src/pages/TransactionDuplicate/ReviewBillable.tsx @@ -19,7 +19,7 @@ function ReviewBillable() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewCategory.tsx b/src/pages/TransactionDuplicate/ReviewCategory.tsx index 5ded413d9f3f..b28cb6863137 100644 --- a/src/pages/TransactionDuplicate/ReviewCategory.tsx +++ b/src/pages/TransactionDuplicate/ReviewCategory.tsx @@ -19,7 +19,7 @@ function ReviewCategory() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index e8ef70f9a70a..d3c379517cf1 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -19,7 +19,7 @@ function ReviewDescription() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewMerchant.tsx b/src/pages/TransactionDuplicate/ReviewMerchant.tsx index 586857f7946f..d49a67d7d911 100644 --- a/src/pages/TransactionDuplicate/ReviewMerchant.tsx +++ b/src/pages/TransactionDuplicate/ReviewMerchant.tsx @@ -19,7 +19,7 @@ function ReviewMerchant() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx index dbddce9d208f..361b92c2af5a 100644 --- a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx +++ b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx @@ -19,7 +19,7 @@ function ReviewReimbursable() { const {translate} = useLocalize(); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewTag.tsx b/src/pages/TransactionDuplicate/ReviewTag.tsx index db1e02db4809..16138865cfd0 100644 --- a/src/pages/TransactionDuplicate/ReviewTag.tsx +++ b/src/pages/TransactionDuplicate/ReviewTag.tsx @@ -21,7 +21,7 @@ function ReviewTag() { const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index ea75dc87c192..90e8c26656c9 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -24,7 +24,7 @@ function ReviewTaxRate() { const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? ''); + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), From 2124c249c02483ddda1aa3fa46a54a084f93b804 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 6 Oct 2024 16:09:18 +0530 Subject: [PATCH 010/235] minor update. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 401e29976f71..3070eaa092a7 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1039,7 +1039,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri .filter((category) => differentValues.includes(category.name)) .map((e) => e.name); - if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && availableCategories.length > 1) { + if ( + !areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && + (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes(''))) + ) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; } } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { From 1709905f776da09c567cc76349763a429de55406 Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 22:42:35 +0530 Subject: [PATCH 011/235] migrate from withOnyx HOC to the useOnyx hook. --- src/components/MapView/MapView.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index cbe604661c05..a72788fcc62f 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -3,7 +3,7 @@ import type {MapState} from '@rnmapbox/maps'; import Mapbox, {MarkerView, setAccessToken} from '@rnmapbox/maps'; import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import useTheme from '@hooks/useTheme'; @@ -21,11 +21,12 @@ import Direction from './Direction'; import type {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps, MapViewOnyxProps} from './types'; +import type {ComponentProps} from './types'; import utils from './utils'; const MapView = forwardRef( - ({accessToken, style, mapPadding, userLocation, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { + ({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { + const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); const navigation = useNavigation(); const {isOffline} = useNetwork(); const {translate} = useLocalize(); @@ -298,8 +299,4 @@ const MapView = forwardRef( }, ); -export default withOnyx({ - userLocation: { - key: ONYXKEYS.USER_LOCATION, - }, -})(memo(MapView)); +export default memo(MapView); From 4bef7003eb8a3655663e3a63f428462db50efd1e Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:02:04 +0530 Subject: [PATCH 012/235] remove userLocation from Props types and migrate from withOnyx HOC to useOnyx hook --- src/components/MapView/MapViewImpl.website.tsx | 13 +++++-------- src/components/MapView/types.ts | 8 ++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/components/MapView/MapViewImpl.website.tsx b/src/components/MapView/MapViewImpl.website.tsx index 7df277671043..5958b2939396 100644 --- a/src/components/MapView/MapViewImpl.website.tsx +++ b/src/components/MapView/MapViewImpl.website.tsx @@ -9,7 +9,7 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, import type {MapRef, ViewState} from 'react-map-gl'; import Map, {Marker} from 'react-map-gl'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import usePrevious from '@hooks/usePrevious'; @@ -29,7 +29,7 @@ import './mapbox.css'; import type {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps, MapViewOnyxProps} from './types'; +import type {ComponentProps} from './types'; import utils from './utils'; const MapViewImpl = forwardRef( @@ -40,13 +40,14 @@ const MapViewImpl = forwardRef( waypoints, mapPadding, accessToken, - userLocation, directionCoordinates, initialState = {location: CONST.MAPBOX.DEFAULT_COORDINATE, zoom: CONST.MAPBOX.DEFAULT_ZOOM}, interactive = true, }, ref, ) => { + const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); + const {isOffline} = useNetwork(); const {translate} = useLocalize(); @@ -295,8 +296,4 @@ const MapViewImpl = forwardRef( }, ); -export default withOnyx({ - userLocation: { - key: ONYXKEYS.USER_LOCATION, - }, -})(MapViewImpl); +export default MapViewImpl; diff --git a/src/components/MapView/types.ts b/src/components/MapView/types.ts index a0494a9ac499..77b1b9eb82c2 100644 --- a/src/components/MapView/types.ts +++ b/src/components/MapView/types.ts @@ -2,10 +2,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import type * as OnyxTypes from '@src/types/onyx'; import type {MapViewProps} from './MapViewTypes'; -type MapViewOnyxProps = { - userLocation: OnyxEntry; -}; +type ComponentProps = MapViewProps; -type ComponentProps = MapViewProps & MapViewOnyxProps; - -export type {MapViewOnyxProps, ComponentProps}; +export type {ComponentProps}; From e7f3b6e14bf2044b274d44dfb24125f7ff2d0ca5 Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:19:45 +0530 Subject: [PATCH 013/235] Address TypeScript errors --- src/components/MapView/MapView.website.tsx | 5 ++--- src/components/MapView/MapViewImpl.website.tsx | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx index 3a28943b575a..52cdaee902a3 100644 --- a/src/components/MapView/MapView.website.tsx +++ b/src/components/MapView/MapView.website.tsx @@ -4,11 +4,10 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle, MapViewProps} from './MapViewTypes'; import PendingMapView from './PendingMapView'; -import type {ComponentProps} from './types'; -const MapView = forwardRef((props, ref) => { +const MapView = forwardRef((props, ref) => { const {isOffline} = useNetwork(); const {translate} = useLocalize(); const styles = useThemeStyles(); diff --git a/src/components/MapView/MapViewImpl.website.tsx b/src/components/MapView/MapViewImpl.website.tsx index 5958b2939396..5f68b041602e 100644 --- a/src/components/MapView/MapViewImpl.website.tsx +++ b/src/components/MapView/MapViewImpl.website.tsx @@ -26,13 +26,12 @@ import getCurrentPosition from '@src/libs/getCurrentPosition'; import ONYXKEYS from '@src/ONYXKEYS'; import Direction from './Direction'; import './mapbox.css'; -import type {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle, MapViewProps} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps} from './types'; import utils from './utils'; -const MapViewImpl = forwardRef( +const MapViewImpl = forwardRef( ( { style, From 3cbc0df8fb4018812d46e92fd52c81b33f86a4ab Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:25:09 +0530 Subject: [PATCH 014/235] deleted extra types file from Mapview --- src/components/MapView/MapView.tsx | 5 ++--- src/components/MapView/types.ts | 7 ------- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 src/components/MapView/types.ts diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index a72788fcc62f..a611c3d62727 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -18,13 +18,12 @@ import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; import ONYXKEYS from '@src/ONYXKEYS'; import Direction from './Direction'; -import type {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle, MapViewProps} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import type {ComponentProps} from './types'; import utils from './utils'; -const MapView = forwardRef( +const MapView = forwardRef( ({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => { const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); const navigation = useNavigation(); diff --git a/src/components/MapView/types.ts b/src/components/MapView/types.ts deleted file mode 100644 index 77b1b9eb82c2..000000000000 --- a/src/components/MapView/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type {OnyxEntry} from 'react-native-onyx'; -import type * as OnyxTypes from '@src/types/onyx'; -import type {MapViewProps} from './MapViewTypes'; - -type ComponentProps = MapViewProps; - -export type {ComponentProps}; From 801c3678937740e61db937a5cc32ae33d236fb79 Mon Sep 17 00:00:00 2001 From: Shahidullah Muffakir Date: Tue, 8 Oct 2024 23:39:53 +0530 Subject: [PATCH 015/235] Remove unused @ts-expect-error directive from MapView --- src/components/MapView/MapView.website.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx index 52cdaee902a3..b89bfa19e98e 100644 --- a/src/components/MapView/MapView.website.tsx +++ b/src/components/MapView/MapView.website.tsx @@ -50,7 +50,6 @@ const MapView = forwardRef((props, ref) => { } > Date: Thu, 10 Oct 2024 00:38:39 +0530 Subject: [PATCH 016/235] minor fix. Signed-off-by: krishna2323 --- src/libs/actions/Policy/Category.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index a49a5e5c733f..78b0f2dec9e2 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1346,10 +1346,6 @@ function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: str API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX, parameters, onyxData); } -function getPolicyCategories(policyID: string) { - return allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {}; -} - export { getPolicyCategories, openPolicyCategoriesPage, @@ -1374,5 +1370,4 @@ export { setPolicyCategoryTax, importPolicyCategories, downloadCategoriesCSV, - getPolicyCategories, }; From d8cecc998c28b73e41c027f6b395cf9b41065663 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 10 Oct 2024 01:05:06 +0530 Subject: [PATCH 017/235] fix category dupe step skip. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 6 +++--- src/libs/actions/Policy/Category.ts | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 3070eaa092a7..cd2c6544b199 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -3,7 +3,7 @@ import lodashIsEqual from 'lodash/isEqual'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import {getPolicyCategories} from '@libs/actions/Policy/Category'; +import {getPolicyCategoriesData} from '@libs/actions/Policy/Category'; import type {TransactionMergeParams} from '@libs/API/parameters'; import {getCurrencyDecimals} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -1034,9 +1034,9 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); - const policyCategories = getPolicyCategories(report?.policyID ?? '-1'); + const policyCategories = getPolicyCategoriesData(report?.policyID ?? '-1'); const availableCategories = Object.values(policyCategories) - .filter((category) => differentValues.includes(category.name)) + .filter((category) => differentValues.includes(category.name) && firstTransaction?.category !== category.name) .map((e) => e.name); if ( diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 78b0f2dec9e2..41771ac5aa0e 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1346,6 +1346,10 @@ function setPolicyCategoryTax(policyID: string, categoryName: string, taxID: str API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX, parameters, onyxData); } +function getPolicyCategoriesData(policyID: string) { + return allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {}; +} + export { getPolicyCategories, openPolicyCategoriesPage, @@ -1370,4 +1374,5 @@ export { setPolicyCategoryTax, importPolicyCategories, downloadCategoriesCSV, + getPolicyCategoriesData, }; From 6d07d46ae17c867d108cab7cdcd77649d76cb8d9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 14 Oct 2024 03:41:22 +0530 Subject: [PATCH 018/235] feat: skip tags review if no valid tags are available. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 35 +++++++++++++++++++++++++----- src/libs/actions/Policy/Tag.ts | 5 +++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index a627a7e61fd9..0e308af2692e 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -4,6 +4,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import {getPolicyCategoriesData} from '@libs/actions/Policy/Category'; +import {getPolicyTagsData} from '@libs/actions/Policy/Tag'; import type {TransactionMergeParams} from '@libs/API/parameters'; import {getCurrencyDecimals} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -1014,6 +1015,8 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const isFirstTransactionCommentEmptyObject = typeof firstTransaction?.comment === 'object' && firstTransaction?.comment?.comment === ''; const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const areAllFieldsEqualForKey = areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')); + if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => getDescription(item) === ''); @@ -1033,8 +1036,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const differentValues = getDifferentValues(transactions, keys); const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); - if (!areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && validTaxes.length > 1) { + if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); @@ -1043,13 +1048,31 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri .filter((category) => differentValues.includes(category.name) && firstTransaction?.category !== category.name) .map((e) => e.name); - if ( - !areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')) && - (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes(''))) - ) { + if (!areAllFieldsEqualForKey && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } + } else if (fieldName === 'tag') { + const policyTags = getPolicyTagsData(report?.policyID ?? '-1'); + const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); + if (isMultiLevelTags) { + if (areAllFieldsEqualForKey) { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } else { + processChanges(fieldName, transactions, keys); + } + } else { + const differentValues = getDifferentValues(transactions, keys); + const policyTagsObj = Object.values(Object.values(policyTags).at(0)?.tags ?? {}); + const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); + if (!areAllFieldsEqualForKey && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { + change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; + } } - } else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { processChanges(fieldName, transactions, keys); diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index 7708921f57b5..772e748ad4f2 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -1040,6 +1040,10 @@ function downloadTagsCSV(policyID: string, onDownloadFailed: () => void) { fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_TAGS_CSV}), fileName, '', false, formData, CONST.NETWORK.METHOD.POST, onDownloadFailed); } +function getPolicyTagsData(policyID: string) { + return allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {}; +} + export { buildOptimisticPolicyRecentlyUsedTags, setPolicyRequiresTag, @@ -1058,6 +1062,7 @@ export { setPolicyTagApprover, importPolicyTags, downloadTagsCSV, + getPolicyTagsData, }; export type {NewCustomUnit}; From aa64f7e326e644586087632e2ee4b757640e6801 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 14 Oct 2024 18:53:43 +0530 Subject: [PATCH 019/235] minor fix. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 0e308af2692e..0a3c18ec7a89 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1038,8 +1038,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; - } else { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); @@ -1050,8 +1048,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; - } else { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'tag') { const policyTags = getPolicyTagsData(report?.policyID ?? '-1'); @@ -1068,8 +1064,6 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); if (!areAllFieldsEqualForKey && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; - } else { - keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } } else if (areAllFieldsEqualForKey) { From 58663255a5c7d8eaf92eb41dd27b93998426bda7 Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 16 Oct 2024 23:49:06 +0500 Subject: [PATCH 020/235] hide expensify from new chat page --- src/libs/OptionsListUtils.ts | 2 +- src/pages/NewChatPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index fbf2f3b94c7c..1ede98338f87 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1994,7 +1994,7 @@ function getOptions( allPersonalDetailsOptions = lodashOrderBy(allPersonalDetailsOptions, [(personalDetail) => personalDetail.text?.toLowerCase()], 'asc'); } - const optionsToExclude: Option[] = []; + const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}]; // If we're including selected options from the search results, we only want to exclude them if the search input is empty // This is because on certain pages, we show the selected options at the top when the search input is empty diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index c406f7f3058c..494e099933fe 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -229,7 +229,7 @@ function NewChatPage({isGroupChat}: NewChatPageProps) { const itemRightSideComponent = useCallback( (item: ListItem & OptionsListUtils.Option, isFocused?: boolean) => { - if (!!item.isSelfDM || (item.accountID && CONST.NON_ADDABLE_ACCOUNT_IDS.includes(item.accountID))) { + if (!!item.isSelfDM || (item.login && excludedGroupEmails.includes(item.login))) { return null; } /** From 039f0d6982b91199c18400bf4bafccfa3a0a6390 Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 17 Oct 2024 00:12:55 +0500 Subject: [PATCH 021/235] fix type error --- src/pages/NewChatPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 494e099933fe..bd3e30a48cf4 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -38,7 +38,7 @@ type NewChatPageProps = { isGroupChat?: boolean; }; -const excludedGroupEmails = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); +const excludedGroupEmails: Array = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); function useOptions({isGroupChat}: NewChatPageProps) { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); From 0fd9a41a1e274fbf11b4d7c0b8aef6f9ade3d4aa Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 17 Oct 2024 00:28:04 +0500 Subject: [PATCH 022/235] fix lint error --- src/pages/NewChatPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index bd3e30a48cf4..5b44659babab 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -38,7 +38,7 @@ type NewChatPageProps = { isGroupChat?: boolean; }; -const excludedGroupEmails: Array = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); +const excludedGroupEmails: string[] = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); function useOptions({isGroupChat}: NewChatPageProps) { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); From 45d852fd4d6a968b82b3e8ef0b87dc87f2136263 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 17 Oct 2024 20:01:02 +0530 Subject: [PATCH 023/235] clear review duplicates data when starting a new flow. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 92444062ced9..5021a3e65c56 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -289,6 +289,7 @@ function MoneyRequestPreviewContent({ const navigateToReviewFields = () => { const backTo = route.params.backTo; + Transaction.abandonReviewDuplicateTransactions(); const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID ?? ''); Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID ?? '', reportID: transaction?.reportID}); From b216356911399a88b313a10da97d31719927555f Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:56:14 +1300 Subject: [PATCH 024/235] Upgrade react-native-web to v0.19.13 --- package-lock.json | 7 ++-- package.json | 2 +- ...eact-native-web+0.19.13+001+initial.patch} | 40 +++++++++---------- ...ative-web+0.19.13+002+fixLastSpacer.patch} | 6 +-- ...eb+0.19.13+003+image-header-support.patch} | 12 +++--- ...web+0.19.13+004+fixPointerEventDown.patch} | 0 ...ive-web+0.19.13+005+osr-improvement.patch} | 20 +++++----- ...13+006+remove-focus-trap-from-modal.patch} | 4 +- ...3+007+fix-scrollable-overflown-text.patch} | 8 ++-- 9 files changed, 50 insertions(+), 49 deletions(-) rename patches/{react-native-web+0.19.12+001+initial.patch => react-native-web+0.19.13+001+initial.patch} (95%) rename patches/{react-native-web+0.19.12+002+fixLastSpacer.patch => react-native-web+0.19.13+002+fixLastSpacer.patch} (94%) rename patches/{react-native-web+0.19.12+003+image-header-support.patch => react-native-web+0.19.13+003+image-header-support.patch} (95%) rename patches/{react-native-web+0.19.12+004+fixPointerEventDown.patch => react-native-web+0.19.13+004+fixPointerEventDown.patch} (100%) rename patches/{react-native-web+0.19.12+005+osr-improvement.patch => react-native-web+0.19.13+005+osr-improvement.patch} (94%) rename patches/{react-native-web+0.19.12+006+remove focus trap from modal.patch => react-native-web+0.19.13+006+remove-focus-trap-from-modal.patch} (88%) rename patches/{react-native-web+0.19.12+007+fix-scrollable-overflown-text.patch => react-native-web+0.19.13+007+fix-scrollable-overflown-text.patch} (84%) diff --git a/package-lock.json b/package-lock.json index 78b95d5ea122..150f2731e3de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,7 +115,7 @@ "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", "react-native-vision-camera": "4.0.0-beta.13", - "react-native-web": "^0.19.12", + "react-native-web": "0.19.13", "react-native-web-sound": "^0.1.3", "react-native-webview": "13.8.6", "react-plaid-link": "3.3.2", @@ -35730,8 +35730,9 @@ } }, "node_modules/react-native-web": { - "version": "0.19.12", - "license": "MIT", + "version": "0.19.13", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.tgz", + "integrity": "sha512-etv3bN8rJglrRCp/uL4p7l8QvUNUC++QwDbdZ8CB7BvZiMvsxfFIRM1j04vxNldG3uo2puRd6OSWR3ibtmc29A==", "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", diff --git a/package.json b/package.json index b1f9b1aba231..b92b68b26b44 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "3.8.0", "react-native-vision-camera": "4.0.0-beta.13", - "react-native-web": "^0.19.12", + "react-native-web": "0.19.13", "react-native-web-sound": "^0.1.3", "react-native-webview": "13.8.6", "react-plaid-link": "3.3.2", diff --git a/patches/react-native-web+0.19.12+001+initial.patch b/patches/react-native-web+0.19.13+001+initial.patch similarity index 95% rename from patches/react-native-web+0.19.12+001+initial.patch rename to patches/react-native-web+0.19.13+001+initial.patch index c77cfc7829ed..75efdf4da117 100644 --- a/patches/react-native-web+0.19.12+001+initial.patch +++ b/patches/react-native-web+0.19.13+001+initial.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js -index e137def..c3e5054 100644 +index 1f52b73..53b1a83 100644 --- a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js +++ b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js -@@ -285,7 +285,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -287,7 +287,7 @@ class VirtualizedList extends StateSafePureComponent { // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. constructor(_props) { @@ -11,7 +11,7 @@ index e137def..c3e5054 100644 super(_props); this._getScrollMetrics = () => { return this._scrollMetrics; -@@ -520,6 +520,11 @@ class VirtualizedList extends StateSafePureComponent { +@@ -522,6 +522,11 @@ class VirtualizedList extends StateSafePureComponent { visibleLength, zoomScale }; @@ -23,7 +23,7 @@ index e137def..c3e5054 100644 this._updateViewableItems(this.props, this.state.cellsAroundViewport); if (!this.props) { return; -@@ -569,7 +574,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -571,7 +576,7 @@ class VirtualizedList extends StateSafePureComponent { this._updateCellsToRender = () => { this._updateViewableItems(this.props, this.state.cellsAroundViewport); this.setState((state, props) => { @@ -32,7 +32,7 @@ index e137def..c3e5054 100644 var renderMask = VirtualizedList._createRenderMask(props, cellsAroundViewport, this._getNonViewportRenderRegions(props)); if (cellsAroundViewport.first === state.cellsAroundViewport.first && cellsAroundViewport.last === state.cellsAroundViewport.last && renderMask.equals(state.renderMask)) { return null; -@@ -589,7 +594,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -591,7 +596,7 @@ class VirtualizedList extends StateSafePureComponent { return { index, item, @@ -41,7 +41,7 @@ index e137def..c3e5054 100644 isViewable }; }; -@@ -621,12 +626,10 @@ class VirtualizedList extends StateSafePureComponent { +@@ -623,12 +628,10 @@ class VirtualizedList extends StateSafePureComponent { }; this._getFrameMetrics = (index, props) => { var data = props.data, @@ -55,7 +55,7 @@ index e137def..c3e5054 100644 if (!frame || frame.index !== index) { if (getItemLayout) { /* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment -@@ -650,7 +653,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -652,7 +655,7 @@ class VirtualizedList extends StateSafePureComponent { // The last cell we rendered may be at a new index. Bail if we don't know // where it is. @@ -64,7 +64,7 @@ index e137def..c3e5054 100644 return []; } var first = focusedCellIndex; -@@ -690,9 +693,15 @@ class VirtualizedList extends StateSafePureComponent { +@@ -692,9 +695,15 @@ class VirtualizedList extends StateSafePureComponent { } } var initialRenderRegion = VirtualizedList._initialRenderRegion(_props); @@ -81,7 +81,7 @@ index e137def..c3e5054 100644 }; // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. -@@ -748,6 +757,26 @@ class VirtualizedList extends StateSafePureComponent { +@@ -750,6 +759,26 @@ class VirtualizedList extends StateSafePureComponent { } } } @@ -108,7 +108,7 @@ index e137def..c3e5054 100644 static _createRenderMask(props, cellsAroundViewport, additionalRegions) { var itemCount = props.getItemCount(props.data); invariant(cellsAroundViewport.first >= 0 && cellsAroundViewport.last >= cellsAroundViewport.first - 1 && cellsAroundViewport.last < itemCount, "Invalid cells around viewport \"[" + cellsAroundViewport.first + ", " + cellsAroundViewport.last + "]\" was passed to VirtualizedList._createRenderMask"); -@@ -796,7 +825,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -798,7 +827,7 @@ class VirtualizedList extends StateSafePureComponent { } } } @@ -117,7 +117,7 @@ index e137def..c3e5054 100644 var data = props.data, getItemCount = props.getItemCount; var onEndReachedThreshold = onEndReachedThresholdOrDefault(props.onEndReachedThreshold); -@@ -819,17 +848,9 @@ class VirtualizedList extends StateSafePureComponent { +@@ -821,17 +850,9 @@ class VirtualizedList extends StateSafePureComponent { last: Math.min(cellsAroundViewport.last + renderAhead, getItemCount(data) - 1) }; } else { @@ -138,7 +138,7 @@ index e137def..c3e5054 100644 return cellsAroundViewport.last >= getItemCount(data) ? VirtualizedList._constrainToItemCount(cellsAroundViewport, props) : cellsAroundViewport; } newCellsAroundViewport = computeWindowedRenderLimits(props, maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch), windowSizeOrDefault(props.windowSize), cellsAroundViewport, this.__getFrameMetricsApprox, this._scrollMetrics); -@@ -902,16 +923,36 @@ class VirtualizedList extends StateSafePureComponent { +@@ -904,16 +925,36 @@ class VirtualizedList extends StateSafePureComponent { } } static getDerivedStateFromProps(newProps, prevState) { @@ -177,7 +177,7 @@ index e137def..c3e5054 100644 }; } _pushCells(cells, stickyHeaderIndices, stickyIndicesFromProps, first, last, inversionStyle) { -@@ -934,7 +975,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -936,7 +977,7 @@ class VirtualizedList extends StateSafePureComponent { last = Math.min(end, last); var _loop = function _loop() { var item = getItem(data, ii); @@ -186,7 +186,7 @@ index e137def..c3e5054 100644 _this._indicesToKeys.set(ii, key); if (stickyIndicesFromProps.has(ii + stickyOffset)) { stickyHeaderIndices.push(cells.length); -@@ -969,20 +1010,23 @@ class VirtualizedList extends StateSafePureComponent { +@@ -971,20 +1012,23 @@ class VirtualizedList extends StateSafePureComponent { } static _constrainToItemCount(cells, props) { var itemCount = props.getItemCount(props.data); @@ -216,8 +216,8 @@ index e137def..c3e5054 100644 if (props.keyExtractor != null) { return props.keyExtractor(item, index); } -@@ -1022,7 +1066,12 @@ class VirtualizedList extends StateSafePureComponent { - cells.push( /*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { +@@ -1024,7 +1068,12 @@ class VirtualizedList extends StateSafePureComponent { + cells.push(/*#__PURE__*/React.createElement(VirtualizedListCellContextProvider, { cellKey: this._getCellKey() + '-header', key: "$header" - }, /*#__PURE__*/React.createElement(View, { @@ -230,7 +230,7 @@ index e137def..c3e5054 100644 onLayout: this._onLayoutHeader, style: [inversionStyle, this.props.ListHeaderComponentStyle] }, -@@ -1124,7 +1173,11 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1126,7 +1175,11 @@ class VirtualizedList extends StateSafePureComponent { // TODO: Android support invertStickyHeaders: this.props.invertStickyHeaders !== undefined ? this.props.invertStickyHeaders : this.props.inverted, stickyHeaderIndices, @@ -243,7 +243,7 @@ index e137def..c3e5054 100644 }); this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1; var innerRet = /*#__PURE__*/React.createElement(VirtualizedListContextProvider, { -@@ -1317,8 +1370,12 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1319,8 +1372,12 @@ class VirtualizedList extends StateSafePureComponent { onStartReached = _this$props8.onStartReached, onStartReachedThreshold = _this$props8.onStartReachedThreshold, onEndReached = _this$props8.onEndReached, @@ -258,7 +258,7 @@ index e137def..c3e5054 100644 var _this$_scrollMetrics2 = this._scrollMetrics, contentLength = _this$_scrollMetrics2.contentLength, visibleLength = _this$_scrollMetrics2.visibleLength, -@@ -1358,16 +1415,10 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1360,16 +1417,10 @@ class VirtualizedList extends StateSafePureComponent { // and call onStartReached only once for a given content length, // and only if onEndReached is not being executed else if (onStartReached != null && this.state.cellsAroundViewport.first === 0 && isWithinStartThreshold && this._scrollMetrics.contentLength !== this._sentStartForContentLength) { @@ -279,7 +279,7 @@ index e137def..c3e5054 100644 } // If the user scrolls away from the start or end and back again, -@@ -1433,6 +1484,11 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1435,6 +1486,11 @@ class VirtualizedList extends StateSafePureComponent { */ _updateViewableItems(props, cellsAroundViewport) { diff --git a/patches/react-native-web+0.19.12+002+fixLastSpacer.patch b/patches/react-native-web+0.19.13+002+fixLastSpacer.patch similarity index 94% rename from patches/react-native-web+0.19.12+002+fixLastSpacer.patch rename to patches/react-native-web+0.19.13+002+fixLastSpacer.patch index 581298613492..c400dcfc8cca 100644 --- a/patches/react-native-web+0.19.12+002+fixLastSpacer.patch +++ b/patches/react-native-web+0.19.13+002+fixLastSpacer.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-web/dist/modules/AccessibilityUtil/propsToAccessibilityComponent.js b/node_modules/react-native-web/dist/modules/AccessibilityUtil/propsToAccessibilityComponent.js -index 9c9a533..7794181 100644 +index 7d1d587..de51afe 100644 --- a/node_modules/react-native-web/dist/modules/AccessibilityUtil/propsToAccessibilityComponent.js +++ b/node_modules/react-native-web/dist/modules/AccessibilityUtil/propsToAccessibilityComponent.js @@ -27,7 +27,8 @@ var roleComponents = { @@ -13,7 +13,7 @@ index 9c9a533..7794181 100644 var emptyObject = {}; var propsToAccessibilityComponent = function propsToAccessibilityComponent(props) { diff --git a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js -index 7f6c880..b05da08 100644 +index 53b1a83..5689220 100644 --- a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js +++ b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js @@ -78,14 +78,6 @@ function scrollEventThrottleOrDefault(scrollEventThrottle) { @@ -31,7 +31,7 @@ index 7f6c880..b05da08 100644 /** * Base implementation for the more convenient [``](https://reactnative.dev/docs/flatlist) -@@ -1107,7 +1099,8 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1109,7 +1101,8 @@ class VirtualizedList extends StateSafePureComponent { _keylessItemComponentName = ''; var spacerKey = this._getSpacerKey(!horizontal); var renderRegions = this.state.renderMask.enumerateRegions(); diff --git a/patches/react-native-web+0.19.12+003+image-header-support.patch b/patches/react-native-web+0.19.13+003+image-header-support.patch similarity index 95% rename from patches/react-native-web+0.19.12+003+image-header-support.patch rename to patches/react-native-web+0.19.13+003+image-header-support.patch index d0a490a4ed70..15e83ce31f8a 100644 --- a/patches/react-native-web+0.19.12+003+image-header-support.patch +++ b/patches/react-native-web+0.19.13+003+image-header-support.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/react-native-web/dist/exports/Image/index.js b/node_modules/react-native-web/dist/exports/Image/index.js -index 9649d27..66ef95c 100644 +index 348831d..ca40ee8 100644 --- a/node_modules/react-native-web/dist/exports/Image/index.js +++ b/node_modules/react-native-web/dist/exports/Image/index.js -@@ -135,7 +135,22 @@ function resolveAssetUri(source) { +@@ -137,7 +137,22 @@ function resolveAssetUri(source) { } return uri; } @@ -13,7 +13,7 @@ index 9649d27..66ef95c 100644 + if (onError) { + onError({ + nativeEvent: { -+ error: "Failed to load resource " + uri + " (404)" ++ error: "Failed to load resource " + uri + } + }); + } @@ -26,14 +26,14 @@ index 9649d27..66ef95c 100644 var _ariaLabel = props['aria-label'], accessibilityLabel = props.accessibilityLabel, blurRadius = props.blurRadius, -@@ -238,16 +253,10 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { +@@ -240,16 +255,10 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { } }, function error() { updateState(ERRORED); - if (onError) { - onError({ - nativeEvent: { -- error: "Failed to load resource " + uri + " (404)" +- error: "Failed to load resource " + uri - } - }); - } @@ -47,7 +47,7 @@ index 9649d27..66ef95c 100644 }); } function abortPendingRequest() { -@@ -279,10 +288,79 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { +@@ -281,10 +290,79 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { suppressHydrationWarning: true }), hiddenImage, createTintColorSVG(tintColor, filterRef.current)); }); diff --git a/patches/react-native-web+0.19.12+004+fixPointerEventDown.patch b/patches/react-native-web+0.19.13+004+fixPointerEventDown.patch similarity index 100% rename from patches/react-native-web+0.19.12+004+fixPointerEventDown.patch rename to patches/react-native-web+0.19.13+004+fixPointerEventDown.patch diff --git a/patches/react-native-web+0.19.12+005+osr-improvement.patch b/patches/react-native-web+0.19.13+005+osr-improvement.patch similarity index 94% rename from patches/react-native-web+0.19.12+005+osr-improvement.patch rename to patches/react-native-web+0.19.13+005+osr-improvement.patch index b1afa699e7a2..d0a952172768 100644 --- a/patches/react-native-web+0.19.12+005+osr-improvement.patch +++ b/patches/react-native-web+0.19.13+005+osr-improvement.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js -index bede95b..2aef4c6 100644 +index 5689220..df40877 100644 --- a/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js +++ b/node_modules/react-native-web/dist/vendor/react-native/VirtualizedList/index.js -@@ -332,7 +332,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -334,7 +334,7 @@ class VirtualizedList extends StateSafePureComponent { zoomScale: 1 }; this._scrollRef = null; @@ -11,7 +11,7 @@ index bede95b..2aef4c6 100644 this._sentEndForContentLength = 0; this._totalCellLength = 0; this._totalCellsMeasured = 0; -@@ -684,16 +684,18 @@ class VirtualizedList extends StateSafePureComponent { +@@ -686,16 +686,18 @@ class VirtualizedList extends StateSafePureComponent { }); } } @@ -32,7 +32,7 @@ index bede95b..2aef4c6 100644 }; // REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller. -@@ -919,13 +921,13 @@ class VirtualizedList extends StateSafePureComponent { +@@ -921,13 +923,13 @@ class VirtualizedList extends StateSafePureComponent { // first and last could be stale (e.g. if a new, shorter items props is passed in), so we make // sure we're rendering a reasonable range here. var itemCount = newProps.getItemCount(newProps.data); @@ -48,7 +48,7 @@ index bede95b..2aef4c6 100644 if (newProps.maintainVisibleContentPosition != null && prevFirstVisibleItemKey != null && newFirstVisibleItemKey != null) { if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) { // Fast path if items were added at the start of the list. -@@ -944,7 +946,8 @@ class VirtualizedList extends StateSafePureComponent { +@@ -946,7 +948,8 @@ class VirtualizedList extends StateSafePureComponent { cellsAroundViewport: constrainedCells, renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells), firstVisibleItemKey: newFirstVisibleItemKey, @@ -58,7 +58,7 @@ index bede95b..2aef4c6 100644 }; } _pushCells(cells, stickyHeaderIndices, stickyIndicesFromProps, first, last, inversionStyle) { -@@ -1220,7 +1223,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1222,7 +1225,7 @@ class VirtualizedList extends StateSafePureComponent { return ret; } } @@ -67,7 +67,7 @@ index bede95b..2aef4c6 100644 var _this$props7 = this.props, data = _this$props7.data, extraData = _this$props7.extraData; -@@ -1244,6 +1247,11 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1246,6 +1249,11 @@ class VirtualizedList extends StateSafePureComponent { if (hiPriInProgress) { this._hiPriInProgress = false; } @@ -79,7 +79,7 @@ index bede95b..2aef4c6 100644 } // Used for preventing scrollToIndex from being called multiple times for initialScrollIndex -@@ -1407,8 +1415,8 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1409,8 +1417,8 @@ class VirtualizedList extends StateSafePureComponent { // Next check if the user just scrolled within the start threshold // and call onStartReached only once for a given content length, // and only if onEndReached is not being executed @@ -90,7 +90,7 @@ index bede95b..2aef4c6 100644 onStartReached({ distanceFromStart }); -@@ -1417,7 +1425,7 @@ class VirtualizedList extends StateSafePureComponent { +@@ -1419,7 +1427,7 @@ class VirtualizedList extends StateSafePureComponent { // If the user scrolls away from the start or end and back again, // cause onStartReached or onEndReached to be triggered again else { @@ -100,7 +100,7 @@ index bede95b..2aef4c6 100644 } } diff --git a/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js b/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js -index 459f017..d20115c 100644 +index 459f017..fb2d269 100644 --- a/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js +++ b/node_modules/react-native-web/src/vendor/react-native/VirtualizedList/index.js @@ -79,6 +79,7 @@ type State = { diff --git a/patches/react-native-web+0.19.12+006+remove focus trap from modal.patch b/patches/react-native-web+0.19.13+006+remove-focus-trap-from-modal.patch similarity index 88% rename from patches/react-native-web+0.19.12+006+remove focus trap from modal.patch rename to patches/react-native-web+0.19.13+006+remove-focus-trap-from-modal.patch index 14dbc88b0b1c..eac73db57e35 100644 --- a/patches/react-native-web+0.19.12+006+remove focus trap from modal.patch +++ b/patches/react-native-web+0.19.13+006+remove-focus-trap-from-modal.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/react-native-web/dist/exports/Modal/index.js b/node_modules/react-native-web/dist/exports/Modal/index.js -index d5df021..e2c46cf 100644 +index a9a7c36..522ef93 100644 --- a/node_modules/react-native-web/dist/exports/Modal/index.js +++ b/node_modules/react-native-web/dist/exports/Modal/index.js -@@ -86,13 +86,11 @@ var Modal = /*#__PURE__*/React.forwardRef((props, forwardedRef) => { +@@ -88,13 +88,11 @@ var Modal = /*#__PURE__*/React.forwardRef((props, forwardedRef) => { onDismiss: onDismissCallback, onShow: onShowCallback, visible: visible diff --git a/patches/react-native-web+0.19.12+007+fix-scrollable-overflown-text.patch b/patches/react-native-web+0.19.13+007+fix-scrollable-overflown-text.patch similarity index 84% rename from patches/react-native-web+0.19.12+007+fix-scrollable-overflown-text.patch rename to patches/react-native-web+0.19.13+007+fix-scrollable-overflown-text.patch index 11b85afcf86c..304a57ad0657 100644 --- a/patches/react-native-web+0.19.12+007+fix-scrollable-overflown-text.patch +++ b/patches/react-native-web+0.19.13+007+fix-scrollable-overflown-text.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/react-native-web/dist/exports/Text/index.js b/node_modules/react-native-web/dist/exports/Text/index.js -index 8c5f79b..4a47f80 100644 +index 4130386..1076f55 100644 --- a/node_modules/react-native-web/dist/exports/Text/index.js +++ b/node_modules/react-native-web/dist/exports/Text/index.js -@@ -166,7 +166,7 @@ var styles = StyleSheet.create({ +@@ -176,7 +176,7 @@ var styles = StyleSheet.create({ textMultiLine: { display: '-webkit-box', maxWidth: '100%', @@ -12,10 +12,10 @@ index 8c5f79b..4a47f80 100644 WebkitBoxOrient: 'vertical' }, diff --git a/node_modules/react-native-web/src/exports/Text/index.js b/node_modules/react-native-web/src/exports/Text/index.js -index 071ae10..e43042c 100644 +index f79e82c..f27ccec 100644 --- a/node_modules/react-native-web/src/exports/Text/index.js +++ b/node_modules/react-native-web/src/exports/Text/index.js -@@ -219,7 +219,7 @@ const styles = StyleSheet.create({ +@@ -223,7 +223,7 @@ const styles = StyleSheet.create({ textMultiLine: { display: '-webkit-box', maxWidth: '100%', From 1d3dee84a0e4a16646cd83cd8c5968d5ad9c92d9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 18 Oct 2024 18:16:58 +0530 Subject: [PATCH 025/235] category/tag review shows when feature is disabled. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 15 ++++++++++----- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 0a3c18ec7a89..919ce85c5c1d 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1014,9 +1014,9 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const firstTransaction = transactions.at(0); const isFirstTransactionCommentEmptyObject = typeof firstTransaction?.comment === 'object' && firstTransaction?.comment?.comment === ''; const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const policy = PolicyUtils.getPolicy(report?.policyID); const areAllFieldsEqualForKey = areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|')); - if (fieldName === 'description') { const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction); const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => getDescription(item) === ''); @@ -1032,12 +1032,13 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri processChanges(fieldName, transactions, keys); } } else if (fieldName === 'taxCode') { - const policy = PolicyUtils.getPolicy(report?.policyID); const differentValues = getDifferentValues(transactions, keys); const validTaxes = differentValues?.filter((taxID) => PolicyUtils.getTaxByID(policy, (taxID as string) ?? '')?.name); if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { const differentValues = getDifferentValues(transactions, keys); @@ -1046,14 +1047,16 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri .filter((category) => differentValues.includes(category.name) && firstTransaction?.category !== category.name) .map((e) => e.name); - if (!areAllFieldsEqualForKey && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { + if (!areAllFieldsEqualForKey && policy?.areCategoriesEnabled && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'tag') { const policyTags = getPolicyTagsData(report?.policyID ?? '-1'); const isMultiLevelTags = PolicyUtils.isMultiLevelTags(policyTags); if (isMultiLevelTags) { - if (areAllFieldsEqualForKey) { + if (areAllFieldsEqualForKey || !policy?.areTagsEnabled) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } else { processChanges(fieldName, transactions, keys); @@ -1062,8 +1065,10 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const differentValues = getDifferentValues(transactions, keys); const policyTagsObj = Object.values(Object.values(policyTags).at(0)?.tags ?? {}); const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); - if (!areAllFieldsEqualForKey && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { + if (!areAllFieldsEqualForKey && policy?.areTagsEnabled && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; + } else { + keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } } else if (areAllFieldsEqualForKey) { diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index 90e8c26656c9..857a93429f00 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -20,10 +20,10 @@ import ReviewFields from './ReviewFields'; function ReviewTaxRate() { const route = useRoute>(); const {translate} = useLocalize(); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reviewDuplicates?.reportID ?? route.params.threadReportID}`); const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( From 363fb75a0c7a097b369046526aacf65c8170abe2 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 19 Oct 2024 04:38:08 +1300 Subject: [PATCH 026/235] Prevent the active cell from being virtualized --- .../SelectionList/BaseSelectionList.tsx | 2 ++ src/components/SelectionList/index.tsx | 19 ++++++++++++++++--- src/components/SelectionList/types.ts | 9 ++++++++- .../settings/Profile/TimezoneSelectPage.tsx | 1 + 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 06bf8eb6434a..50b90bf3f180 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -105,6 +105,7 @@ function BaseSelectionList( shouldIgnoreFocus = false, scrollEventThrottle, contentContainerStyle, + CellRendererComponent, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -758,6 +759,7 @@ function BaseSelectionList( onEndReachedThreshold={onEndReachedThreshold} scrollEventThrottle={scrollEventThrottle} contentContainerStyle={contentContainerStyle} + CellRendererComponent={CellRendererComponent} /> {children} diff --git a/src/components/SelectionList/index.tsx b/src/components/SelectionList/index.tsx index fc788a7e2b4b..8d2f009c0599 100644 --- a/src/components/SelectionList/index.tsx +++ b/src/components/SelectionList/index.tsx @@ -1,12 +1,23 @@ import React, {forwardRef, useEffect, useState} from 'react'; -import type {ForwardedRef} from 'react'; -import {Keyboard} from 'react-native'; +import type {FocusEventHandler, ForwardedRef} from 'react'; +import {Keyboard, View} from 'react-native'; +import type {CellRendererProps} from 'react-native'; import * as Browser from '@libs/Browser'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import BaseSelectionList from './BaseSelectionList'; import type {BaseSelectionListProps, ListItem, SelectionListHandle} from './types'; -function SelectionList({onScroll, ...props}: BaseSelectionListProps, ref: ForwardedRef) { +function FocusAwareCellRenderer({onFocusCapture, ...rest}: CellRendererProps) { + return ( + + ); +} + +function SelectionList({onScroll, shouldPreventActiveCellVirtualization, ...props}: BaseSelectionListProps, ref: ForwardedRef) { const [isScreenTouched, setIsScreenTouched] = useState(false); const touchStart = () => setIsScreenTouched(true); @@ -46,6 +57,8 @@ function SelectionList({onScroll, ...props}: BaseSelecti // Ignore the focus if it's caused by a touch event on mobile chrome. // For example, a long press will trigger a focus event on mobile chrome. shouldIgnoreFocus={Browser.isMobileChrome() && isScreenTouched} + // Customize the cell renderer so the VirtualizedList can receive focus events and avoid virtualizing the active cell. + CellRendererComponent={shouldPreventActiveCellVirtualization ? FocusAwareCellRenderer : undefined} /> ); } diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index d90f329dbf4c..58e4f55fe0ce 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -1,5 +1,6 @@ -import type {MutableRefObject, ReactElement, ReactNode} from 'react'; +import type {ComponentType, MutableRefObject, ReactElement, ReactNode} from 'react'; import type { + CellRendererProps, GestureResponderEvent, InputModeOptions, LayoutChangeEvent, @@ -584,6 +585,12 @@ type BaseSelectionListProps = Partial & { /** Additional styles to apply to scrollable content */ contentContainerStyle?: StyleProp; + + /** Custom cell wrapper */ + CellRendererComponent?: ComponentType>; + + /** Whether to prevent the active cell from being virtualized and losing focus in browsers */ + shouldPreventActiveCellVirtualization?: boolean; } & TRightHandSideComponent; type SelectionListHandle = { diff --git a/src/pages/settings/Profile/TimezoneSelectPage.tsx b/src/pages/settings/Profile/TimezoneSelectPage.tsx index 326db5481d37..cee713065de7 100644 --- a/src/pages/settings/Profile/TimezoneSelectPage.tsx +++ b/src/pages/settings/Profile/TimezoneSelectPage.tsx @@ -78,6 +78,7 @@ function TimezoneSelectPage({currentUserPersonalDetails}: TimezoneSelectPageProp showScrollIndicator shouldShowTooltips={false} ListItem={RadioListItem} + shouldPreventActiveCellVirtualization /> ); From 92510d03885d6b9df307dca2ed7c8da0e120ab59 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 19 Oct 2024 05:21:28 +1300 Subject: [PATCH 027/235] Refactor FocuseAwareCellRendererComponent --- .../SelectionList/BaseSelectionList.tsx | 5 +++-- .../index.native.tsx | 3 +++ .../FocusAwareCellRendererComponent/index.tsx | 20 +++++++++++++++++++ src/components/SelectionList/index.tsx | 19 +++--------------- src/components/SelectionList/types.ts | 3 --- 5 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 src/components/SelectionList/FocusAwareCellRendererComponent/index.native.tsx create mode 100644 src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 50b90bf3f180..82ea3b80a284 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -30,6 +30,7 @@ import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import arraysEqual from '@src/utils/arraysEqual'; import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, SectionListDataType, SectionWithIndexOffset, SelectionListHandle} from './types'; +import FocusAwareCellRendererComponent from './FocusAwareCellRendererComponent'; const getDefaultItemHeight = () => variables.optionRowHeight; @@ -105,7 +106,7 @@ function BaseSelectionList( shouldIgnoreFocus = false, scrollEventThrottle, contentContainerStyle, - CellRendererComponent, + shouldPreventActiveCellVirtualization = false, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -759,7 +760,7 @@ function BaseSelectionList( onEndReachedThreshold={onEndReachedThreshold} scrollEventThrottle={scrollEventThrottle} contentContainerStyle={contentContainerStyle} - CellRendererComponent={CellRendererComponent} + CellRendererComponent={shouldPreventActiveCellVirtualization ? FocusAwareCellRendererComponent : undefined} /> {children} diff --git a/src/components/SelectionList/FocusAwareCellRendererComponent/index.native.tsx b/src/components/SelectionList/FocusAwareCellRendererComponent/index.native.tsx new file mode 100644 index 000000000000..94833b707acd --- /dev/null +++ b/src/components/SelectionList/FocusAwareCellRendererComponent/index.native.tsx @@ -0,0 +1,3 @@ +const FocusAwareCellRendererComponent = undefined; + +export default FocusAwareCellRendererComponent; diff --git a/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx b/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx new file mode 100644 index 000000000000..573b83c50a09 --- /dev/null +++ b/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import {View} from 'react-native'; +import type {CellRendererProps} from 'react-native'; +import type {FocusEventHandler} from 'react'; +import type {ListItem} from '../types'; + +function FocusAwareCellRendererComponent({onFocusCapture, ...rest}: CellRendererProps) { + return ( + + ); +} + +FocusAwareCellRendererComponent.displayName = 'FocusAwareCellRendererComponent'; + +export default FocusAwareCellRendererComponent; diff --git a/src/components/SelectionList/index.tsx b/src/components/SelectionList/index.tsx index 8d2f009c0599..fc788a7e2b4b 100644 --- a/src/components/SelectionList/index.tsx +++ b/src/components/SelectionList/index.tsx @@ -1,23 +1,12 @@ import React, {forwardRef, useEffect, useState} from 'react'; -import type {FocusEventHandler, ForwardedRef} from 'react'; -import {Keyboard, View} from 'react-native'; -import type {CellRendererProps} from 'react-native'; +import type {ForwardedRef} from 'react'; +import {Keyboard} from 'react-native'; import * as Browser from '@libs/Browser'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import BaseSelectionList from './BaseSelectionList'; import type {BaseSelectionListProps, ListItem, SelectionListHandle} from './types'; -function FocusAwareCellRenderer({onFocusCapture, ...rest}: CellRendererProps) { - return ( - - ); -} - -function SelectionList({onScroll, shouldPreventActiveCellVirtualization, ...props}: BaseSelectionListProps, ref: ForwardedRef) { +function SelectionList({onScroll, ...props}: BaseSelectionListProps, ref: ForwardedRef) { const [isScreenTouched, setIsScreenTouched] = useState(false); const touchStart = () => setIsScreenTouched(true); @@ -57,8 +46,6 @@ function SelectionList({onScroll, shouldPreventActiveCel // Ignore the focus if it's caused by a touch event on mobile chrome. // For example, a long press will trigger a focus event on mobile chrome. shouldIgnoreFocus={Browser.isMobileChrome() && isScreenTouched} - // Customize the cell renderer so the VirtualizedList can receive focus events and avoid virtualizing the active cell. - CellRendererComponent={shouldPreventActiveCellVirtualization ? FocusAwareCellRenderer : undefined} /> ); } diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 58e4f55fe0ce..4133c6ce3823 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -586,9 +586,6 @@ type BaseSelectionListProps = Partial & { /** Additional styles to apply to scrollable content */ contentContainerStyle?: StyleProp; - /** Custom cell wrapper */ - CellRendererComponent?: ComponentType>; - /** Whether to prevent the active cell from being virtualized and losing focus in browsers */ shouldPreventActiveCellVirtualization?: boolean; } & TRightHandSideComponent; From fcb6ad09ad5ba58d3f30c3e7ce50f2ca0a880911 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 19 Oct 2024 05:26:48 +1300 Subject: [PATCH 028/235] Update imports --- src/components/SelectionList/BaseSelectionList.tsx | 2 +- .../SelectionList/FocusAwareCellRendererComponent/index.tsx | 4 ++-- src/components/SelectionList/types.ts | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 82ea3b80a284..fa7f31da7626 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -29,8 +29,8 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import arraysEqual from '@src/utils/arraysEqual'; -import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, SectionListDataType, SectionWithIndexOffset, SelectionListHandle} from './types'; import FocusAwareCellRendererComponent from './FocusAwareCellRendererComponent'; +import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, SectionListDataType, SectionWithIndexOffset, SelectionListHandle} from './types'; const getDefaultItemHeight = () => variables.optionRowHeight; diff --git a/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx b/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx index 573b83c50a09..35e5a57d60a0 100644 --- a/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx +++ b/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx @@ -1,8 +1,8 @@ import React from 'react'; import {View} from 'react-native'; -import type {CellRendererProps} from 'react-native'; import type {FocusEventHandler} from 'react'; -import type {ListItem} from '../types'; +import type {CellRendererProps} from 'react-native'; +import type {ListItem} from '@components/SelectionList/types'; function FocusAwareCellRendererComponent({onFocusCapture, ...rest}: CellRendererProps) { return ( diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 4133c6ce3823..2782f1cfa2cd 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -1,6 +1,5 @@ -import type {ComponentType, MutableRefObject, ReactElement, ReactNode} from 'react'; +import type {MutableRefObject, ReactElement, ReactNode} from 'react'; import type { - CellRendererProps, GestureResponderEvent, InputModeOptions, LayoutChangeEvent, From 120f7abb13ae3919e17a5d58241867f29faf1c78 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sat, 19 Oct 2024 05:31:31 +1300 Subject: [PATCH 029/235] Update imports --- .../SelectionList/FocusAwareCellRendererComponent/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx b/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx index 35e5a57d60a0..1df71d0fcea9 100644 --- a/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx +++ b/src/components/SelectionList/FocusAwareCellRendererComponent/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {View} from 'react-native'; import type {FocusEventHandler} from 'react'; +import {View} from 'react-native'; import type {CellRendererProps} from 'react-native'; import type {ListItem} from '@components/SelectionList/types'; From 166363deccf92f3f4bad280e59b67d63df33595c Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:42:13 +1300 Subject: [PATCH 030/235] Improve SelectionList item rendering performance --- src/CONST.ts | 1 + .../SelectionList/BaseSelectionList.tsx | 138 +++++++++--------- .../BaseSelectionListItemRenderer.tsx | 89 +++++++++++ src/components/SelectionList/index.tsx | 29 ++++ src/components/SelectionList/types.ts | 3 + 5 files changed, 191 insertions(+), 69 deletions(-) create mode 100644 src/components/SelectionList/BaseSelectionListItemRenderer.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 84003710938a..be64e5108ef7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1135,6 +1135,7 @@ const CONST = { SEARCH_BUILD_TREE: 'search_build_tree', SEARCH_FILTER_OPTIONS: 'search_filter_options', USE_DEBOUNCED_STATE_DELAY: 300, + LIST_SCROLLING_DEBOUNCE_TIME: 200, }, PRIORITY_MODE: { GSD: 'gsd', diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 262959d7f218..dca738248b39 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -1,4 +1,5 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; +import lodashDebounce from 'lodash/debounce'; import isEmpty from 'lodash/isEmpty'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; @@ -24,11 +25,11 @@ import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; import getSectionsWithIndexOffset from '@libs/getSectionsWithIndexOffset'; import Log from '@libs/Log'; -import * as SearchUtils from '@libs/SearchUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import arraysEqual from '@src/utils/arraysEqual'; +import BaseSelectionListItemRenderer from './BaseSelectionListItemRenderer'; import FocusAwareCellRendererComponent from './FocusAwareCellRendererComponent'; import type {BaseSelectionListProps, ButtonOrCheckBoxRoles, FlattenedSectionsReturn, ListItem, SectionListDataType, SectionWithIndexOffset, SelectionListHandle} from './types'; @@ -106,6 +107,7 @@ function BaseSelectionList( shouldIgnoreFocus = false, scrollEventThrottle, contentContainerStyle, + shouldDebounceScrolling = false, shouldPreventActiveCellVirtualization = false, }: BaseSelectionListProps, ref: ForwardedRef, @@ -275,6 +277,8 @@ function BaseSelectionList( // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [flattenedSections.disabledArrowKeyOptionsIndexes]); + const debouncedScrollToIndex = useMemo(() => lodashDebounce(scrollToIndex, CONST.TIMING.LIST_SCROLLING_DEBOUNCE_TIME, {leading: true, trailing: true}), [scrollToIndex]); + // If `initiallyFocusedOptionKey` is not passed, we fall back to `-1`, to avoid showing the highlight on the first member const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({ initialFocusedIndex: flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey), @@ -282,7 +286,7 @@ function BaseSelectionList( disabledIndexes: disabledArrowKeyIndexes, isActive: isFocused, onFocusedIndexChange: (index: number) => { - scrollToIndex(index, true); + (shouldDebounceScrolling ? debouncedScrollToIndex : scrollToIndex)(index, true); }, isFocused, }); @@ -297,36 +301,50 @@ function BaseSelectionList( * @param item - the list item * @param indexToFocus - the list item index to focus */ - const selectRow = (item: TItem, indexToFocus?: number) => { - // In single-selection lists we don't care about updating the focused index, because the list is closed after selecting an item - if (canSelectMultiple) { - if (sections.length > 1) { - // If the list has only 1 section (e.g. Workspace Members list), we do nothing. - // If the list has multiple sections (e.g. Workspace Invite list), and `shouldUnfocusRow` is false, - // we focus the first one after all the selected (selected items are always at the top). - const selectedOptionsCount = item.isSelected ? flattenedSections.selectedOptions.length - 1 : flattenedSections.selectedOptions.length + 1; - - if (!item.isSelected) { - // If we're selecting an item, scroll to it's position at the top, so we can see it - scrollToIndex(Math.max(selectedOptionsCount - 1, 0), true); + const selectRow = useCallback( + (item: TItem, indexToFocus?: number) => { + // In single-selection lists we don't care about updating the focused index, because the list is closed after selecting an item + if (canSelectMultiple) { + if (sections.length > 1) { + // If the list has only 1 section (e.g. Workspace Members list), we do nothing. + // If the list has multiple sections (e.g. Workspace Invite list), and `shouldUnfocusRow` is false, + // we focus the first one after all the selected (selected items are always at the top). + const selectedOptionsCount = item.isSelected ? flattenedSections.selectedOptions.length - 1 : flattenedSections.selectedOptions.length + 1; + + if (!item.isSelected) { + // If we're selecting an item, scroll to it's position at the top, so we can see it + scrollToIndex(Math.max(selectedOptionsCount - 1, 0), true); + } } - } - if (shouldShowTextInput) { - clearInputAfterSelect(); + if (shouldShowTextInput) { + clearInputAfterSelect(); + } } - } - if (shouldUpdateFocusedIndex && typeof indexToFocus === 'number') { - setFocusedIndex(indexToFocus); - } + if (shouldUpdateFocusedIndex && typeof indexToFocus === 'number') { + setFocusedIndex(indexToFocus); + } - onSelectRow(item); + onSelectRow(item); - if (shouldShowTextInput && shouldPreventDefaultFocusOnSelectRow && innerTextInputRef.current) { - innerTextInputRef.current.focus(); - } - }; + if (shouldShowTextInput && shouldPreventDefaultFocusOnSelectRow && innerTextInputRef.current) { + innerTextInputRef.current.focus(); + } + }, + [ + canSelectMultiple, + sections.length, + flattenedSections.selectedOptions.length, + scrollToIndex, + shouldShowTextInput, + clearInputAfterSelect, + shouldUpdateFocusedIndex, + setFocusedIndex, + onSelectRow, + shouldPreventDefaultFocusOnSelectRow, + ], + ); const selectAllRow = () => { onSelectAll?.(); @@ -438,50 +456,32 @@ function BaseSelectionList( // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. const showTooltip = shouldShowTooltips && normalizedIndex < 10; - const handleOnCheckboxPress = () => { - if (SearchUtils.isReportListItemType(item)) { - return onCheckboxPress; - } - return onCheckboxPress ? () => onCheckboxPress(item) : undefined; - }; - return ( - <> - { - if (shouldSingleExecuteRowSelect) { - singleExecution(() => selectRow(item, index))(); - } else { - selectRow(item, index); - } - }} - onCheckboxPress={handleOnCheckboxPress()} - onDismissError={() => onDismissError?.(item)} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} - // We're already handling the Enter key press in the useKeyboardShortcut hook, so we don't want the list item to submit the form - shouldPreventEnterKeySubmit - rightHandSideComponent={rightHandSideComponent} - keyForList={item.keyForList ?? ''} - isMultilineSupported={isRowMultilineSupported} - isAlternateTextMultilineSupported={isAlternateTextMultilineSupported} - alternateTextNumberOfLines={alternateTextNumberOfLines} - onFocus={() => { - if (shouldIgnoreFocus || isDisabled) { - return; - } - setFocusedIndex(normalizedIndex); - }} - shouldSyncFocus={!isTextInputFocusedRef.current} - wrapperStyle={listItemWrapperStyle} - /> - {item.footerContent && item.footerContent} - + ); }; diff --git a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx new file mode 100644 index 000000000000..4697ac87eef0 --- /dev/null +++ b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import type useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; +import type useSingleExecution from '@hooks/useSingleExecution'; +import * as SearchUtils from '@libs/SearchUtils'; +import type {BaseListItemProps, BaseSelectionListProps, ListItem} from './types'; + +function BaseSelectionListItemRenderer({ + ListItem, + item, + index, + isFocused, + isDisabled, + showTooltip, + canSelectMultiple, + onLongPressRow, + shouldSingleExecuteRowSelect, + selectRow, + onCheckboxPress, + onDismissError, + shouldPreventDefaultFocusOnSelectRow, + rightHandSideComponent, + isMultilineSupported, + isAlternateTextMultilineSupported, + alternateTextNumberOfLines, + shouldIgnoreFocus, + setFocusedIndex, + normalizedIndex, + shouldSyncFocus, + wrapperStyle, + singleExecution, +}: Omit, 'onSelectRow'> & + Pick, 'ListItem' | 'shouldIgnoreFocus' | 'shouldSingleExecuteRowSelect'> & { + index: number; + selectRow: (item: TItem, indexToFocus?: number) => void; + setFocusedIndex: ReturnType[1]; + normalizedIndex: number; + singleExecution: ReturnType['singleExecution']; + }) { + const handleOnCheckboxPress = () => { + if (SearchUtils.isReportListItemType(item)) { + return onCheckboxPress; + } + return onCheckboxPress ? () => onCheckboxPress(item) : undefined; + }; + + return ( + <> + { + if (shouldSingleExecuteRowSelect) { + singleExecution(() => selectRow(item, index))(); + } else { + selectRow(item, index); + } + }} + onCheckboxPress={handleOnCheckboxPress()} + onDismissError={() => onDismissError?.(item)} + shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} + // We're already handling the Enter key press in the useKeyboardShortcut hook, so we don't want the list item to submit the form + shouldPreventEnterKeySubmit + rightHandSideComponent={rightHandSideComponent} + keyForList={item.keyForList ?? ''} + isMultilineSupported={isMultilineSupported} + isAlternateTextMultilineSupported={isAlternateTextMultilineSupported} + alternateTextNumberOfLines={alternateTextNumberOfLines} + onFocus={() => { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (shouldIgnoreFocus || isDisabled) { + return; + } + setFocusedIndex(normalizedIndex); + }} + shouldSyncFocus={shouldSyncFocus} + wrapperStyle={wrapperStyle} + /> + {item.footerContent && item.footerContent} + + ); +} + +BaseSelectionListItemRenderer.displayName = 'BaseSelectionListItemRenderer'; + +export default BaseSelectionListItemRenderer; diff --git a/src/components/SelectionList/index.tsx b/src/components/SelectionList/index.tsx index fc788a7e2b4b..ecb63fc31e74 100644 --- a/src/components/SelectionList/index.tsx +++ b/src/components/SelectionList/index.tsx @@ -3,6 +3,7 @@ import type {ForwardedRef} from 'react'; import {Keyboard} from 'react-native'; import * as Browser from '@libs/Browser'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import CONST from '@src/CONST'; import BaseSelectionList from './BaseSelectionList'; import type {BaseSelectionListProps, ListItem, SelectionListHandle} from './types'; @@ -28,6 +29,33 @@ function SelectionList({onScroll, ...props}: BaseSelecti }; }, []); + const [shouldDebounceScrolling, setShouldDebounceScrolling] = useState(false); + + const checkShouldDebounceScrolling = (event: KeyboardEvent) => { + if (!event) { + return; + } + + // Moving through items using the keyboard triggers scrolling by the browser, so we debounce programmatic scrolling to prevent jittering. + if ( + event.key === CONST.KEYBOARD_SHORTCUTS.ARROW_DOWN.shortcutKey || + event.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey || + event.key === CONST.KEYBOARD_SHORTCUTS.TAB.shortcutKey + ) { + setShouldDebounceScrolling(event.type === 'keydown'); + } + }; + + useEffect(() => { + document.addEventListener('keydown', checkShouldDebounceScrolling, {passive: true}); + document.addEventListener('keyup', checkShouldDebounceScrolling, {passive: true}); + + return () => { + document.removeEventListener('keydown', checkShouldDebounceScrolling); + document.removeEventListener('keyup', checkShouldDebounceScrolling); + }; + }, []); + // In SearchPageBottomTab we use useAnimatedScrollHandler from reanimated(for performance reasons) and it returns object instead of function. In that case we cannot change it to a function call, that's why we have to choose between onScroll and defaultOnScroll. const defaultOnScroll = () => { // Only dismiss the keyboard whenever the user scrolls the screen @@ -46,6 +74,7 @@ function SelectionList({onScroll, ...props}: BaseSelecti // Ignore the focus if it's caused by a touch event on mobile chrome. // For example, a long press will trigger a focus event on mobile chrome. shouldIgnoreFocus={Browser.isMobileChrome() && isScreenTouched} + shouldDebounceScrolling={shouldDebounceScrolling} /> ); } diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 2782f1cfa2cd..039f9ecb2316 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -585,6 +585,9 @@ type BaseSelectionListProps = Partial & { /** Additional styles to apply to scrollable content */ contentContainerStyle?: StyleProp; + /** Whether to debounce scrolling on focused index change */ + shouldDebounceScrolling?: boolean; + /** Whether to prevent the active cell from being virtualized and losing focus in browsers */ shouldPreventActiveCellVirtualization?: boolean; } & TRightHandSideComponent; From 01079a5fd7ca561c2a463807bedf6ca12c7bb5df Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Sun, 20 Oct 2024 04:41:54 +1300 Subject: [PATCH 031/235] Define prop types --- .../BaseSelectionListItemRenderer.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx index 4697ac87eef0..3a740b4aee94 100644 --- a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx +++ b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx @@ -4,6 +4,15 @@ import type useSingleExecution from '@hooks/useSingleExecution'; import * as SearchUtils from '@libs/SearchUtils'; import type {BaseListItemProps, BaseSelectionListProps, ListItem} from './types'; +type BaseSelectionListItemRendererProps = Omit, 'onSelectRow'> & + Pick, 'ListItem' | 'shouldIgnoreFocus' | 'shouldSingleExecuteRowSelect'> & { + index: number; + selectRow: (item: TItem, indexToFocus?: number) => void; + setFocusedIndex: ReturnType[1]; + normalizedIndex: number; + singleExecution: ReturnType['singleExecution']; + }; + function BaseSelectionListItemRenderer({ ListItem, item, @@ -28,14 +37,7 @@ function BaseSelectionListItemRenderer({ shouldSyncFocus, wrapperStyle, singleExecution, -}: Omit, 'onSelectRow'> & - Pick, 'ListItem' | 'shouldIgnoreFocus' | 'shouldSingleExecuteRowSelect'> & { - index: number; - selectRow: (item: TItem, indexToFocus?: number) => void; - setFocusedIndex: ReturnType[1]; - normalizedIndex: number; - singleExecution: ReturnType['singleExecution']; - }) { +}: BaseSelectionListItemRendererProps) { const handleOnCheckboxPress = () => { if (SearchUtils.isReportListItemType(item)) { return onCheckboxPress; From 80fd4e774bb51f3aec476f6da5ae2a8f65bb8340 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Wed, 23 Oct 2024 10:50:28 +0200 Subject: [PATCH 032/235] add recentSearchHash --- src/components/Search/types.ts | 2 ++ src/libs/SearchQueryUtils.ts | 17 +++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 4f96090be9d0..43ebbed24943 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -76,6 +76,8 @@ type SearchQueryAST = { type SearchQueryJSON = { inputQuery: SearchQueryString; hash: number; + /** Hash used for putting queries in recent searches list. It ignores sortOrder and sortBy, because we want to treat queries differing only in sort params as the same query */ + recentSearchHash: number; flatFilters: QueryFilters; } & SearchQueryAST; diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 32c2eff72007..7c7395ef1a9b 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -208,15 +208,13 @@ function findIDFromDisplayValue(filterName: ValueOf Date: Wed, 23 Oct 2024 11:40:38 +0200 Subject: [PATCH 033/235] Revert "Revert "[HybridApp] Allow classic experience users to use NewDot travel page"" This reverts commit 3d342ee74e418637451323a17cc54170aafbe659. --- src/ONYXKEYS.ts | 4 ++ src/components/InitialURLContextProvider.tsx | 7 +-- src/components/ScreenWrapper.tsx | 13 ++++- src/hooks/useOnboardingFlow.ts | 8 +-- src/libs/TripReservationUtils.ts | 25 +++++++-- src/libs/actions/Link.ts | 6 ++- src/libs/actions/Session/index.ts | 53 +++++++++++++------- tests/perf-test/ReportScreen.perf-test.tsx | 4 +- tests/perf-test/SearchRouter.perf-test.tsx | 2 + 9 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d083a46d7760..14c0dc4abc50 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -441,6 +441,9 @@ const ONYXKEYS = { /** Stores recently used currencies */ RECENTLY_USED_CURRENCIES: 'nvp_recentlyUsedCurrencies', + /** States whether we transitioned from OldDot to show only certain group of screens. It should be undefined on pure NewDot. */ + IS_SINGLE_NEW_DOT_ENTRY: 'isSingleNewDotEntry', + /** Company cards custom names */ NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES: 'nvp_expensify_ccCustomNames', @@ -1004,6 +1007,7 @@ type OnyxValuesMapping = { [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; [ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet; [ONYXKEYS.LAST_ROUTE]: string; + [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined; [ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean; [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx index 85ad54ca6c94..f026f2de53f9 100644 --- a/src/components/InitialURLContextProvider.tsx +++ b/src/components/InitialURLContextProvider.tsx @@ -31,9 +31,10 @@ function InitialURLContextProvider({children, url}: InitialURLContextProviderPro useEffect(() => { if (url) { - const route = signInAfterTransitionFromOldDot(url); - setInitialURL(route); - setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); + signInAfterTransitionFromOldDot(url).then((route) => { + setInitialURL(route); + setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); + }); return; } Linking.getInitialURL().then((initURL) => { diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index 152a14fc3eb7..967d366aaa7c 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -1,9 +1,9 @@ -import {useIsFocused, useNavigation} from '@react-navigation/native'; +import {UNSTABLE_usePreventRemove, useIsFocused, useNavigation, useRoute} from '@react-navigation/native'; import type {StackNavigationProp} from '@react-navigation/stack'; import type {ForwardedRef, ReactNode} from 'react'; import React, {createContext, forwardRef, useEffect, useMemo, useRef, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; -import {Keyboard, PanResponder, View} from 'react-native'; +import {Keyboard, NativeModules, PanResponder, View} from 'react-native'; import {PickerAvoidingView} from 'react-native-picker-select'; import type {EdgeInsets} from 'react-native-safe-area-context'; import useEnvironment from '@hooks/useEnvironment'; @@ -164,6 +164,15 @@ function ScreenWrapper( isKeyboardShownRef.current = keyboardState?.isKeyboardShown ?? false; + const route = useRoute(); + const shouldReturnToOldDot = useMemo(() => { + return !!route?.params && 'singleNewDotEntry' in route.params && route.params.singleNewDotEntry === 'true'; + }, [route]); + + UNSTABLE_usePreventRemove(shouldReturnToOldDot, () => { + NativeModules.HybridAppModule?.closeReactNativeApp(false, false); + }); + const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponderCapture: (_e, gestureState) => gestureState.numberActiveTouches === CONST.TEST_TOOL.NUMBER_OF_TAPS, diff --git a/src/hooks/useOnboardingFlow.ts b/src/hooks/useOnboardingFlow.ts index 5ccd3bab9378..9e97c552a6e0 100644 --- a/src/hooks/useOnboardingFlow.ts +++ b/src/hooks/useOnboardingFlow.ts @@ -21,14 +21,16 @@ function useOnboardingFlowRouter() { selector: hasCompletedHybridAppOnboardingFlowSelector, }); + const [isSingleNewDotEntry, isSingleNewDotEntryMetadata] = useOnyx(ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY); + useEffect(() => { - if (isLoadingOnyxValue(isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata)) { + if (isLoadingOnyxValue(isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata, isSingleNewDotEntryMetadata)) { return; } if (NativeModules.HybridAppModule) { // When user is transitioning from OldDot to NewDot, we usually show the explanation modal - if (isHybridAppOnboardingCompleted === false) { + if (isHybridAppOnboardingCompleted === false && !isSingleNewDotEntry) { Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT); } @@ -43,7 +45,7 @@ function useOnboardingFlowRouter() { if (!NativeModules.HybridAppModule && isOnboardingCompleted === false) { OnboardingFlow.startOnboardingFlow(); } - }, [isOnboardingCompleted, isHybridAppOnboardingCompleted, isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata]); + }, [isOnboardingCompleted, isHybridAppOnboardingCompleted, isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata, isSingleNewDotEntryMetadata, isSingleNewDotEntry]); return {isOnboardingCompleted, isHybridAppOnboardingCompleted}; } diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts index b7f754f9cac6..f2ce5113af81 100644 --- a/src/libs/TripReservationUtils.ts +++ b/src/libs/TripReservationUtils.ts @@ -1,5 +1,6 @@ import {Str} from 'expensify-common'; import type {Dispatch, SetStateAction} from 'react'; +import {NativeModules} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; @@ -13,6 +14,7 @@ import type Transaction from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import * as Link from './actions/Link'; +import Log from './Log'; import Navigation from './Navigation/Navigation'; import * as PolicyUtils from './PolicyUtils'; @@ -40,6 +42,14 @@ Onyx.connect({ }, }); +let isSingleNewDotEntry: boolean | undefined; +Onyx.connect({ + key: ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY, + callback: (val) => { + isSingleNewDotEntry = val; + }, +}); + function getTripReservationIcon(reservationType: ReservationType): IconAsset { switch (reservationType) { case CONST.RESERVATION_TYPE.FLIGHT: @@ -91,8 +101,17 @@ function bookATrip(translate: LocaleContextProps['translate'], setCtaErrorMessag if (ctaErrorMessage) { setCtaErrorMessage(''); } - Link.openTravelDotLink(activePolicyID)?.catch(() => { - setCtaErrorMessage(translate('travel.errorMessage')); - }); + Link.openTravelDotLink(activePolicyID) + ?.then(() => { + if (!NativeModules.HybridAppModule || !isSingleNewDotEntry) { + return; + } + + Log.info('[HybridApp] Returning to OldDot after opening TravelDot'); + NativeModules.HybridAppModule.closeReactNativeApp(false, false); + }) + ?.catch(() => { + setCtaErrorMessage(translate('travel.errorMessage')); + }); } export {getTripReservationIcon, getReservationsFromTripTransactions, getTripEReceiptIcon, bookATrip}; diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 13fcea0df85d..4cda676d89e8 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -111,7 +111,7 @@ function openTravelDotLink(policyID: OnyxEntry, postLoginPath?: string) policyID, }; - return new Promise((_, reject) => { + return new Promise((resolve, reject) => { const error = new Error('Failed to generate spotnana token.'); asyncOpenURL( @@ -122,7 +122,9 @@ function openTravelDotLink(policyID: OnyxEntry, postLoginPath?: string) reject(error); throw error; } - return buildTravelDotURL(response.spotnanaToken, postLoginPath); + const travelURL = buildTravelDotURL(response.spotnanaToken, postLoginPath); + resolve(undefined); + return travelURL; }) .catch(() => { reject(error); diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 37488442525d..d75c5064f93a 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -483,28 +483,43 @@ function signUpUser() { function signInAfterTransitionFromOldDot(transitionURL: string) { const [route, queryParams] = transitionURL.split('?'); - const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding} = Object.fromEntries( - queryParams.split('&').map((param) => { - const [key, value] = param.split('='); - return [key, value]; - }), - ); - - const setSessionDataAndOpenApp = () => { - Onyx.multiSet({ - [ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)}, - [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword}, - [ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}}, - }).then(App.openApp); + const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding, isSingleNewDotEntry, primaryLogin} = + Object.fromEntries( + queryParams.split('&').map((param) => { + const [key, value] = param.split('='); + return [key, value]; + }), + ); + + const clearOnyxForNewAccount = () => { + if (clearOnyxOnStart !== 'true') { + return Promise.resolve(); + } + + return Onyx.clear(KEYS_TO_PRESERVE); }; - if (clearOnyxOnStart === 'true') { - Onyx.clear(KEYS_TO_PRESERVE).then(setSessionDataAndOpenApp); - } else { - setSessionDataAndOpenApp(); - } + const setSessionDataAndOpenApp = new Promise((resolve) => { + clearOnyxForNewAccount() + .then(() => + Onyx.multiSet({ + [ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)}, + [ONYXKEYS.ACCOUNT]: {primaryLogin}, + [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword}, + [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: isSingleNewDotEntry === 'true', + [ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}}, + }), + ) + .then(App.openApp) + .catch((error) => { + Log.hmmm('[HybridApp] Initialization of HybridApp has failed. Forcing transition', {error}); + }) + .finally(() => { + resolve(`${route}?singleNewDotEntry=${isSingleNewDotEntry}` as Route); + }); + }); - return route as Route; + return setSessionDataAndOpenApp; } /** diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx index 550b6adabc36..b43004fcc82b 100644 --- a/tests/perf-test/ReportScreen.perf-test.tsx +++ b/tests/perf-test/ReportScreen.perf-test.tsx @@ -118,6 +118,8 @@ jest.mock('@react-navigation/native', () => { useFocusEffect: jest.fn(), useIsFocused: () => true, useRoute: () => jest.fn(), + // eslint-disable-next-line @typescript-eslint/naming-convention + UNSTABLE_usePreventRemove: () => jest.fn(), useNavigation: () => ({ navigate: jest.fn(), addListener: () => jest.fn(), @@ -231,7 +233,6 @@ test('[ReportScreen] should render ReportScreen', async () => { ...reportCollectionDataSet, ...reportActionsCollectionDataSet, }); - await measureRenders( { ...reportCollectionDataSet, ...reportActionsCollectionDataSet, }); - await measureRenders( { useFocusEffect: jest.fn(), useIsFocused: () => true, useRoute: () => jest.fn(), + // eslint-disable-next-line @typescript-eslint/naming-convention + UNSTABLE_usePreventRemove: () => jest.fn(), useNavigation: () => ({ navigate: jest.fn(), addListener: () => jest.fn(), From 0bc8694afda1c3e7411695c59764593803386e60 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 23 Oct 2024 16:36:50 +0530 Subject: [PATCH 034/235] minor update. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index cf10aa35025f..d25c56451dba 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1085,7 +1085,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && validTaxes.length > 1) { change[fieldName] = validTaxes; - } else { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'category') { @@ -1097,7 +1097,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri if (!areAllFieldsEqualForKey && policy?.areCategoriesEnabled && (availableCategories.length > 1 || (availableCategories.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableCategories, ...(differentValues.includes('') ? [''] : [])]; - } else { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } else if (fieldName === 'tag') { @@ -1115,7 +1115,7 @@ function compareDuplicateTransactionFields(transactionID: string, reportID: stri const availableTags = policyTagsObj.filter((tag) => differentValues.includes(tag.name) && firstTransaction?.tag !== tag.name).map((e) => e.name); if (!areAllFieldsEqualForKey && policy?.areTagsEnabled && (availableTags.length > 1 || (availableTags.length === 1 && differentValues.includes('')))) { change[fieldName] = [...availableTags, ...(differentValues.includes('') ? [''] : [])]; - } else { + } else if (areAllFieldsEqualForKey) { keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]]; } } From a42aadcd277284a09fafa5638b73ae5d9b9276d3 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 23 Oct 2024 13:14:42 +0200 Subject: [PATCH 035/235] Fix bug when user cannot return from Travel page --- src/libs/Navigation/NavigationRoot.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index bb005fc6b763..c23c3783b3bf 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -108,7 +108,8 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady, sh } // If there is no lastVisitedPath, we can do early return. We won't modify the default behavior. - if (!lastVisitedPath) { + // The same applies to HybridApp, as we always define the route to which we want to transition. + if (!lastVisitedPath || NativeModules.HybridAppModule) { return undefined; } From 1a6950b7c7909d251c5257ed2661fcc66285c803 Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 24 Oct 2024 04:52:37 +0500 Subject: [PATCH 036/235] remove is group chat --- src/pages/NewChatPage.tsx | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 97712a0fa9d4..c28290e353e7 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -34,13 +34,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SelectedParticipant} from '@src/types/onyx/NewGroupChatDraft'; -type NewChatPageProps = { - isGroupChat?: boolean; -}; - const excludedGroupEmails: string[] = CONST.EXPENSIFY_EMAILS.filter((value) => value !== CONST.EMAIL.CONCIERGE); -function useOptions({isGroupChat}: NewChatPageProps) { +function useOptions() { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedOptions, setSelectedOptions] = useState>([]); const [betas] = useOnyx(ONYXKEYS.BETAS); @@ -57,22 +53,20 @@ function useOptions({isGroupChat}: NewChatPageProps) { personalDetails: listOptions.personalDetails ?? [], betas: betas ?? [], selectedOptions, - excludeLogins: isGroupChat ? excludedGroupEmails : [], maxRecentReportsToShow: 0, includeSelfDM: true, }); return filteredOptions; - }, [betas, isGroupChat, listOptions.personalDetails, listOptions.reports, selectedOptions]); + }, [betas, listOptions.personalDetails, listOptions.reports, selectedOptions]); const options = useMemo(() => { const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, { selectedOptions, - excludeLogins: isGroupChat ? excludedGroupEmails : [], maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); return filteredOptions; - }, [debouncedSearchTerm, defaultOptions, isGroupChat, selectedOptions]); + }, [debouncedSearchTerm, defaultOptions, selectedOptions]); const cleanSearchTerm = useMemo(() => debouncedSearchTerm.trim().toLowerCase(), [debouncedSearchTerm]); const headerMessage = useMemo(() => { return OptionsListUtils.getHeaderMessage( @@ -129,7 +123,7 @@ function useOptions({isGroupChat}: NewChatPageProps) { }; } -function NewChatPage({isGroupChat}: NewChatPageProps) { +function NewChatPage() { const {translate} = useLocalize(); const {isOffline} = useNetwork(); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only @@ -142,9 +136,7 @@ function NewChatPage({isGroupChat}: NewChatPageProps) { const selectionListRef = useRef(null); const {headerMessage, searchTerm, debouncedSearchTerm, setSearchTerm, selectedOptions, setSelectedOptions, recentReports, personalDetails, userToInvite, areOptionsInitialized} = - useOptions({ - isGroupChat, - }); + useOptions(); const [sections, firstKeyForList] = useMemo(() => { const sectionsList: OptionsListUtils.CategorySection[] = []; From a3b151358cbc0fd1167bba702c23c34e111483e4 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:54:29 +0100 Subject: [PATCH 037/235] fix(debug mode): undefined reportID when viewing cause for RBR in LHN --- src/libs/WorkspacesSettingsUtils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index a27d518fe727..1c78515f76e8 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -125,8 +125,14 @@ function getChatTabBrickRoadReport(policyID?: string): OnyxEntry { return undefined; } + // There are optimistic reports without a reportID, so we need to extract it from the report key + const normalizedAllReports = Object.entries(allReports).map(([key, report]) => ({ + ...report, + reportID: report?.reportID ?? key.replace(ONYXKEYS.COLLECTION.REPORT, ''), + })); + // If policyID is undefined, then all reports are checked whether they contain any brick road - const policyReports = policyID ? Object.values(allReports).filter((report) => report?.policyID === policyID) : Object.values(allReports); + const policyReports = policyID ? normalizedAllReports.filter((report) => report?.policyID === policyID) : normalizedAllReports; let reportWithGBR: OnyxEntry; From 4b2d8ad39fc8f1099bc659de398a1137c8c1a82f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:55:00 +0100 Subject: [PATCH 038/235] fix(debug mode): missing reportID in optimistic report --- src/libs/actions/Report.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4af2357fc572..a7f0dd10d621 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -767,10 +767,13 @@ function openReport( return; } - const optimisticReport = reportActionsExist(reportID) - ? {} + const optimisticReport: Report = reportActionsExist(reportID) + ? { + reportID, + } : { reportName: ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]?.reportName ?? CONST.REPORT.DEFAULT_REPORT_NAME, + reportID, }; const optimisticData: OnyxUpdate[] = [ From 8982be0306992b014e1433af355811a104efa94a Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:55:47 +0100 Subject: [PATCH 039/235] feat(debug mode): redirect user to debug report page when viewing cause for RBR in LHN --- .../createCustomBottomTabNavigator/DebugTabView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx index 3e5803b797dc..95ca6d5f35f7 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/DebugTabView.tsx @@ -134,7 +134,7 @@ function DebugTabView({selectedTab = '', chatTabBrickRoad, activeWorkspaceID}: D const report = getChatTabBrickRoadReport(activeWorkspaceID); if (report) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID)); + Navigation.navigate(ROUTES.DEBUG_REPORT.getRoute(report.reportID)); } } if (selectedTab === SCREENS.SETTINGS.ROOT) { From 39b1dbf2db920b31247bb0e9f2837c62e8799884 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 24 Oct 2024 16:56:22 +0100 Subject: [PATCH 040/235] feat(debug mode): add button to view report from debug report page --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/pages/Debug/Report/DebugReportPage.tsx | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4050993a23f2..0eef59b22d7e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5123,6 +5123,7 @@ const translations = { RBR: 'RBR', true: 'true', false: 'false', + viewReport: 'View Report', reasonVisibleInLHN: { hasDraftComment: 'Has draft comment', hasGBR: 'Has GBR', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1db6616571e2..f083d22f045b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5639,6 +5639,7 @@ const translations = { RBR: 'RBR', true: 'verdadero', false: 'falso', + viewReport: 'Ver Informe', reasonVisibleInLHN: { hasDraftComment: 'Tiene comentario en borrador', hasGBR: 'Tiene GBR', diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index fe26fed0c9c0..47d9def1f379 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; import Text from '@components/Text'; @@ -157,6 +158,13 @@ function DebugReportPage({ )} ))} +